refactor(legacy_auth): migrate sign_up to Riverpod

This commit is contained in:
2026-04-22 23:29:38 +02:00
parent 76782fbfd4
commit dc7325ea65
8 changed files with 578 additions and 492 deletions

View File

@@ -0,0 +1,278 @@
import 'dart:async';
import 'dart:ui';
import 'package:legacy_auth/src/core/providers/sign_up_repository_provider.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/legacy_signup_error_event.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/mixins/sign_up_form_validation.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/providers/sign_up_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
part 'sign_up_controller.g.dart';
const int _lastIndex = 1;
@Riverpod(keepAlive: true)
class SignUpController extends _$SignUpController with SignUpFormValidation {
@override
SignUpState build() => const SignUpState();
void setFirstName(String value) {
if (value == state.firstName) return;
state = state.copyWith(firstName: value, validationErrorKey: '');
}
void setLastName(String value) {
if (value == state.lastName) return;
state = state.copyWith(lastName: value, validationErrorKey: '');
}
void setPhone(String value) {
if (value == state.phone) return;
state = state.copyWith(
phone: value,
phoneError: state.showErrors
? phoneErrorFor(value, state.isoCode)
: state.phoneError,
);
}
void setEmail(String value) {
if (value == state.email) return;
state = state.copyWith(
email: value,
validationErrorKey: '',
emailError: state.showErrors ? emailErrorFor(value) : state.emailError,
);
}
void setPassword(String value) {
if (value == state.password) return;
state = state.copyWith(
password: value,
validationErrorKey: '',
passwordError: state.showErrors
? passwordErrorFor(
password: value,
repeatPassword: state.repeatPassword,
)
: state.passwordError,
);
}
void setRepeatPassword(String value) {
if (value == state.repeatPassword) return;
state = state.copyWith(
repeatPassword: value,
validationErrorKey: '',
passwordError: state.showErrors
? passwordErrorFor(
password: state.password,
repeatPassword: value,
)
: state.passwordError,
);
}
void updateCountry(String isoCode) {
if (isoCode == state.isoCode) return;
state = state.copyWith(
isoCode: isoCode,
phoneError: state.showErrors && state.phone.isNotEmpty
? phoneErrorFor(state.phone, isoCode)
: state.phoneError,
);
}
void setAcceptTerms(bool value) {
if (value == state.acceptTerms) return;
state = state.copyWith(acceptTerms: value, validationErrorKey: '');
}
void toggleShowPassword() {
state = state.copyWith(isShowPassword: !state.isShowPassword);
}
void clearApiError() {
if (state.apiErrorEvent != null) {
state = state.copyWith(apiErrorEvent: null);
}
}
void clearValidationError() {
if (state.validationErrorKey.isNotEmpty) {
state = state.copyWith(validationErrorKey: '');
}
}
void next() {
if (state.isLoading) return;
final currentStep = state.currentIndex;
final tracking = ref.read(sfTrackingProvider);
final ok = switch (currentStep) {
0 => _validateStep0(),
1 => _validateStep1(),
_ => true,
};
if (!ok) {
unawaited(tracking.legacyAuthSignupStepValidationFailed(currentStep));
return;
}
unawaited(tracking.legacyAuthSignupStepCompleted(currentStep));
if (currentStep >= _lastIndex) {
unawaited(signUp());
return;
}
state = state.copyWith(
currentIndex: (currentStep + 1).clamp(0, _lastIndex),
);
}
void back() {
if (state.isLoading) return;
if (state.currentIndex <= 0) return;
unawaited(
ref
.read(sfTrackingProvider)
.legacyAuthSignupStepBack(state.currentIndex),
);
state = state.copyWith(
currentIndex: (state.currentIndex - 1).clamp(0, _lastIndex),
);
}
bool _validateStep0() {
final emailErr = emailErrorFor(state.email);
final phoneErr = phoneErrorFor(state.phone, state.isoCode);
state = state.copyWith(
showErrors: true,
emailError: emailErr,
phoneError: phoneErr,
validationErrorKey: '',
);
if (state.firstName.trim().isEmpty) {
state = state.copyWith(validationErrorKey: I18n.errorFirstNameRequired);
return false;
}
if (!isNameValid(state.firstName)) {
state = state.copyWith(validationErrorKey: I18n.errorNameInvalidChars);
return false;
}
if (state.lastName.trim().isEmpty) {
state = state.copyWith(validationErrorKey: I18n.errorLastNameRequired);
return false;
}
if (!isNameValid(state.lastName)) {
state = state.copyWith(validationErrorKey: I18n.errorNameInvalidChars);
return false;
}
if (phoneErr.isNotEmpty) {
state = state.copyWith(validationErrorKey: phoneErr);
return false;
}
if (emailErr.isNotEmpty) {
state = state.copyWith(validationErrorKey: emailErr);
return false;
}
if (!state.acceptTerms) {
state = state.copyWith(validationErrorKey: I18n.errorAcceptTerms);
return false;
}
return true;
}
bool _validateStep1() {
final passwordErr = passwordErrorFor(
password: state.password,
repeatPassword: state.repeatPassword,
);
state = state.copyWith(
showErrors: true,
passwordError: passwordErr,
validationErrorKey: passwordErr,
);
return passwordErr.isEmpty;
}
bool _validateForm() => _validateStep0() && _validateStep1();
Future<bool> signUp() async {
if (state.isLoading) return false;
if (!_validateForm()) return false;
final parsedPhone = SfPhoneNumber.tryParse(
state.phone,
defaultIsoCode: state.isoCode,
);
if (parsedPhone == null) {
state = state.copyWith(
validationErrorKey: I18n.errorMessagePhoneIsInvalid,
phoneError: I18n.errorMessagePhoneIsInvalid,
);
return false;
}
state = state.copyWith(
isLoading: true,
validationErrorKey: '',
apiErrorEvent: null,
showAccountCreated: false,
);
final tracking = ref.read(sfTrackingProvider);
unawaited(tracking.legacyAuthSignupStarted());
try {
final request = LegacySignUpRequestEntity(
firstName: state.firstName.trim().toUpperCase(),
lastName: state.lastName.trim().toUpperCase(),
email: state.email.trim(),
phone: parsedPhone.e164,
language: _deviceLanguage(),
password: state.password,
);
final response = await ref
.read(legacySignUpRepositoryProvider)
.signUp(request: request);
unawaited(tracking.legacyAuthSignupCompleted());
state = state.copyWith(
isLoading: false,
isCreated: response.isCreated,
showAccountCreated: true,
);
return true;
} catch (e) {
unawaited(tracking.legacyAuthSignupFailed(formatErrorMessage(e)));
state = state.copyWith(
isLoading: false,
apiErrorEvent: mapSignupError(e),
);
return false;
}
}
String _deviceLanguage() {
final code = PlatformDispatcher.instance.locale.languageCode
.trim()
.toLowerCase();
return code.isEmpty ? 'es' : code;
}
}

View File

@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sign_up_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SignUpController)
const signUpControllerProvider = SignUpControllerProvider._();
final class SignUpControllerProvider
extends $NotifierProvider<SignUpController, SignUpState> {
const SignUpControllerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'signUpControllerProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$signUpControllerHash();
@$internal
@override
SignUpController create() => SignUpController();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(SignUpState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<SignUpState>(value),
);
}
}
String _$signUpControllerHash() => r'bbc80db2b0e7644b0a8764b77cac9d12eebb2372';
abstract class _$SignUpController extends $Notifier<SignUpState> {
SignUpState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<SignUpState, SignUpState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<SignUpState, SignUpState>,
SignUpState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -1,25 +1,21 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/legacy_signup_error_event.dart';
part 'sign_up_view_state.freezed.dart';
part 'sign_up_state.freezed.dart';
@freezed
abstract class LegacySignUpViewState with _$LegacySignUpViewState {
const factory LegacySignUpViewState({
abstract class SignUpState with _$SignUpState {
const factory SignUpState({
@Default(0) int currentIndex,
@Default('') String firstName,
@Default('') String lastName,
@Default('') String email,
@Default('') String phone,
@Default('ES') String isoCode,
@Default('') String password,
@Default('') String repeatPassword,
@Default(false) bool isShowPassword,
@Default(false) bool acceptTerms,
@Default('') String emailError,
@Default('') String passwordError,
@Default('') String phoneError,
@@ -29,5 +25,5 @@ abstract class LegacySignUpViewState with _$LegacySignUpViewState {
@Default(false) bool showErrors,
@Default(false) bool isCreated,
@Default(false) bool showAccountCreated,
}) = _LegacySignUpViewState;
}) = _SignUpState;
}

View File

@@ -3,7 +3,7 @@
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'sign_up_view_state.dart';
part of 'sign_up_state.dart';
// **************************************************************************
// FreezedGenerator
@@ -12,20 +12,20 @@ part of 'sign_up_view_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LegacySignUpViewState {
mixin _$SignUpState {
int get currentIndex; String get firstName; String get lastName; String get email; String get phone; String get isoCode; String get password; String get repeatPassword; bool get isShowPassword; bool get acceptTerms; String get emailError; String get passwordError; String get phoneError; String get validationErrorKey; LegacySignupErrorEvent? get apiErrorEvent; bool get isLoading; bool get showErrors; bool get isCreated; bool get showAccountCreated;
/// Create a copy of LegacySignUpViewState
/// Create a copy of SignUpState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LegacySignUpViewStateCopyWith<LegacySignUpViewState> get copyWith => _$LegacySignUpViewStateCopyWithImpl<LegacySignUpViewState>(this as LegacySignUpViewState, _$identity);
$SignUpStateCopyWith<SignUpState> get copyWith => _$SignUpStateCopyWithImpl<SignUpState>(this as SignUpState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacySignUpViewState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.isShowPassword, isShowPassword) || other.isShowPassword == isShowPassword)&&(identical(other.acceptTerms, acceptTerms) || other.acceptTerms == acceptTerms)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isCreated, isCreated) || other.isCreated == isCreated)&&(identical(other.showAccountCreated, showAccountCreated) || other.showAccountCreated == showAccountCreated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SignUpState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.isShowPassword, isShowPassword) || other.isShowPassword == isShowPassword)&&(identical(other.acceptTerms, acceptTerms) || other.acceptTerms == acceptTerms)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isCreated, isCreated) || other.isCreated == isCreated)&&(identical(other.showAccountCreated, showAccountCreated) || other.showAccountCreated == showAccountCreated));
}
@@ -34,15 +34,15 @@ int get hashCode => Object.hashAll([runtimeType,currentIndex,firstName,lastName,
@override
String toString() {
return 'LegacySignUpViewState(currentIndex: $currentIndex, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone, isoCode: $isoCode, password: $password, repeatPassword: $repeatPassword, isShowPassword: $isShowPassword, acceptTerms: $acceptTerms, emailError: $emailError, passwordError: $passwordError, phoneError: $phoneError, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, showErrors: $showErrors, isCreated: $isCreated, showAccountCreated: $showAccountCreated)';
return 'SignUpState(currentIndex: $currentIndex, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone, isoCode: $isoCode, password: $password, repeatPassword: $repeatPassword, isShowPassword: $isShowPassword, acceptTerms: $acceptTerms, emailError: $emailError, passwordError: $passwordError, phoneError: $phoneError, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, showErrors: $showErrors, isCreated: $isCreated, showAccountCreated: $showAccountCreated)';
}
}
/// @nodoc
abstract mixin class $LegacySignUpViewStateCopyWith<$Res> {
factory $LegacySignUpViewStateCopyWith(LegacySignUpViewState value, $Res Function(LegacySignUpViewState) _then) = _$LegacySignUpViewStateCopyWithImpl;
abstract mixin class $SignUpStateCopyWith<$Res> {
factory $SignUpStateCopyWith(SignUpState value, $Res Function(SignUpState) _then) = _$SignUpStateCopyWithImpl;
@useResult
$Res call({
int currentIndex, String firstName, String lastName, String email, String phone, String isoCode, String password, String repeatPassword, bool isShowPassword, bool acceptTerms, String emailError, String passwordError, String phoneError, String validationErrorKey, LegacySignupErrorEvent? apiErrorEvent, bool isLoading, bool showErrors, bool isCreated, bool showAccountCreated
@@ -53,14 +53,14 @@ $Res call({
}
/// @nodoc
class _$LegacySignUpViewStateCopyWithImpl<$Res>
implements $LegacySignUpViewStateCopyWith<$Res> {
_$LegacySignUpViewStateCopyWithImpl(this._self, this._then);
class _$SignUpStateCopyWithImpl<$Res>
implements $SignUpStateCopyWith<$Res> {
_$SignUpStateCopyWithImpl(this._self, this._then);
final LegacySignUpViewState _self;
final $Res Function(LegacySignUpViewState) _then;
final SignUpState _self;
final $Res Function(SignUpState) _then;
/// Create a copy of LegacySignUpViewState
/// Create a copy of SignUpState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? currentIndex = null,Object? firstName = null,Object? lastName = null,Object? email = null,Object? phone = null,Object? isoCode = null,Object? password = null,Object? repeatPassword = null,Object? isShowPassword = null,Object? acceptTerms = null,Object? emailError = null,Object? passwordError = null,Object? phoneError = null,Object? validationErrorKey = null,Object? apiErrorEvent = freezed,Object? isLoading = null,Object? showErrors = null,Object? isCreated = null,Object? showAccountCreated = null,}) {
return _then(_self.copyWith(
@@ -90,8 +90,8 @@ as bool,
}
/// Adds pattern-matching-related methods to [LegacySignUpViewState].
extension LegacySignUpViewStatePatterns on LegacySignUpViewState {
/// Adds pattern-matching-related methods to [SignUpState].
extension SignUpStatePatterns on SignUpState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
@@ -104,10 +104,10 @@ extension LegacySignUpViewStatePatterns on LegacySignUpViewState {
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LegacySignUpViewState value)? $default,{required TResult orElse(),}){
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SignUpState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LegacySignUpViewState() when $default != null:
case _SignUpState() when $default != null:
return $default(_that);case _:
return orElse();
@@ -126,10 +126,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LegacySignUpViewState value) $default,){
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SignUpState value) $default,){
final _that = this;
switch (_that) {
case _LegacySignUpViewState():
case _SignUpState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
@@ -147,10 +147,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LegacySignUpViewState value)? $default,){
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SignUpState value)? $default,){
final _that = this;
switch (_that) {
case _LegacySignUpViewState() when $default != null:
case _SignUpState() when $default != null:
return $default(_that);case _:
return null;
@@ -170,7 +170,7 @@ return $default(_that);case _:
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int currentIndex, String firstName, String lastName, String email, String phone, String isoCode, String password, String repeatPassword, bool isShowPassword, bool acceptTerms, String emailError, String passwordError, String phoneError, String validationErrorKey, LegacySignupErrorEvent? apiErrorEvent, bool isLoading, bool showErrors, bool isCreated, bool showAccountCreated)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LegacySignUpViewState() when $default != null:
case _SignUpState() when $default != null:
return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_that.phone,_that.isoCode,_that.password,_that.repeatPassword,_that.isShowPassword,_that.acceptTerms,_that.emailError,_that.passwordError,_that.phoneError,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.showErrors,_that.isCreated,_that.showAccountCreated);case _:
return orElse();
@@ -191,7 +191,7 @@ return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_t
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int currentIndex, String firstName, String lastName, String email, String phone, String isoCode, String password, String repeatPassword, bool isShowPassword, bool acceptTerms, String emailError, String passwordError, String phoneError, String validationErrorKey, LegacySignupErrorEvent? apiErrorEvent, bool isLoading, bool showErrors, bool isCreated, bool showAccountCreated) $default,) {final _that = this;
switch (_that) {
case _LegacySignUpViewState():
case _SignUpState():
return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_that.phone,_that.isoCode,_that.password,_that.repeatPassword,_that.isShowPassword,_that.acceptTerms,_that.emailError,_that.passwordError,_that.phoneError,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.showErrors,_that.isCreated,_that.showAccountCreated);case _:
throw StateError('Unexpected subclass');
@@ -211,7 +211,7 @@ return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_t
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int currentIndex, String firstName, String lastName, String email, String phone, String isoCode, String password, String repeatPassword, bool isShowPassword, bool acceptTerms, String emailError, String passwordError, String phoneError, String validationErrorKey, LegacySignupErrorEvent? apiErrorEvent, bool isLoading, bool showErrors, bool isCreated, bool showAccountCreated)? $default,) {final _that = this;
switch (_that) {
case _LegacySignUpViewState() when $default != null:
case _SignUpState() when $default != null:
return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_that.phone,_that.isoCode,_that.password,_that.repeatPassword,_that.isShowPassword,_that.acceptTerms,_that.emailError,_that.passwordError,_that.phoneError,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.showErrors,_that.isCreated,_that.showAccountCreated);case _:
return null;
@@ -223,8 +223,8 @@ return $default(_that.currentIndex,_that.firstName,_that.lastName,_that.email,_t
/// @nodoc
class _LegacySignUpViewState implements LegacySignUpViewState {
const _LegacySignUpViewState({this.currentIndex = 0, this.firstName = '', this.lastName = '', this.email = '', this.phone = '', this.isoCode = 'ES', this.password = '', this.repeatPassword = '', this.isShowPassword = false, this.acceptTerms = false, this.emailError = '', this.passwordError = '', this.phoneError = '', this.validationErrorKey = '', this.apiErrorEvent, this.isLoading = false, this.showErrors = false, this.isCreated = false, this.showAccountCreated = false});
class _SignUpState implements SignUpState {
const _SignUpState({this.currentIndex = 0, this.firstName = '', this.lastName = '', this.email = '', this.phone = '', this.isoCode = 'ES', this.password = '', this.repeatPassword = '', this.isShowPassword = false, this.acceptTerms = false, this.emailError = '', this.passwordError = '', this.phoneError = '', this.validationErrorKey = '', this.apiErrorEvent, this.isLoading = false, this.showErrors = false, this.isCreated = false, this.showAccountCreated = false});
@override@JsonKey() final int currentIndex;
@@ -247,17 +247,17 @@ class _LegacySignUpViewState implements LegacySignUpViewState {
@override@JsonKey() final bool isCreated;
@override@JsonKey() final bool showAccountCreated;
/// Create a copy of LegacySignUpViewState
/// Create a copy of SignUpState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LegacySignUpViewStateCopyWith<_LegacySignUpViewState> get copyWith => __$LegacySignUpViewStateCopyWithImpl<_LegacySignUpViewState>(this, _$identity);
_$SignUpStateCopyWith<_SignUpState> get copyWith => __$SignUpStateCopyWithImpl<_SignUpState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacySignUpViewState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.isShowPassword, isShowPassword) || other.isShowPassword == isShowPassword)&&(identical(other.acceptTerms, acceptTerms) || other.acceptTerms == acceptTerms)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isCreated, isCreated) || other.isCreated == isCreated)&&(identical(other.showAccountCreated, showAccountCreated) || other.showAccountCreated == showAccountCreated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SignUpState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.isShowPassword, isShowPassword) || other.isShowPassword == isShowPassword)&&(identical(other.acceptTerms, acceptTerms) || other.acceptTerms == acceptTerms)&&(identical(other.emailError, emailError) || other.emailError == emailError)&&(identical(other.passwordError, passwordError) || other.passwordError == passwordError)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.showErrors, showErrors) || other.showErrors == showErrors)&&(identical(other.isCreated, isCreated) || other.isCreated == isCreated)&&(identical(other.showAccountCreated, showAccountCreated) || other.showAccountCreated == showAccountCreated));
}
@@ -266,15 +266,15 @@ int get hashCode => Object.hashAll([runtimeType,currentIndex,firstName,lastName,
@override
String toString() {
return 'LegacySignUpViewState(currentIndex: $currentIndex, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone, isoCode: $isoCode, password: $password, repeatPassword: $repeatPassword, isShowPassword: $isShowPassword, acceptTerms: $acceptTerms, emailError: $emailError, passwordError: $passwordError, phoneError: $phoneError, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, showErrors: $showErrors, isCreated: $isCreated, showAccountCreated: $showAccountCreated)';
return 'SignUpState(currentIndex: $currentIndex, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone, isoCode: $isoCode, password: $password, repeatPassword: $repeatPassword, isShowPassword: $isShowPassword, acceptTerms: $acceptTerms, emailError: $emailError, passwordError: $passwordError, phoneError: $phoneError, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, showErrors: $showErrors, isCreated: $isCreated, showAccountCreated: $showAccountCreated)';
}
}
/// @nodoc
abstract mixin class _$LegacySignUpViewStateCopyWith<$Res> implements $LegacySignUpViewStateCopyWith<$Res> {
factory _$LegacySignUpViewStateCopyWith(_LegacySignUpViewState value, $Res Function(_LegacySignUpViewState) _then) = __$LegacySignUpViewStateCopyWithImpl;
abstract mixin class _$SignUpStateCopyWith<$Res> implements $SignUpStateCopyWith<$Res> {
factory _$SignUpStateCopyWith(_SignUpState value, $Res Function(_SignUpState) _then) = __$SignUpStateCopyWithImpl;
@override @useResult
$Res call({
int currentIndex, String firstName, String lastName, String email, String phone, String isoCode, String password, String repeatPassword, bool isShowPassword, bool acceptTerms, String emailError, String passwordError, String phoneError, String validationErrorKey, LegacySignupErrorEvent? apiErrorEvent, bool isLoading, bool showErrors, bool isCreated, bool showAccountCreated
@@ -285,17 +285,17 @@ $Res call({
}
/// @nodoc
class __$LegacySignUpViewStateCopyWithImpl<$Res>
implements _$LegacySignUpViewStateCopyWith<$Res> {
__$LegacySignUpViewStateCopyWithImpl(this._self, this._then);
class __$SignUpStateCopyWithImpl<$Res>
implements _$SignUpStateCopyWith<$Res> {
__$SignUpStateCopyWithImpl(this._self, this._then);
final _LegacySignUpViewState _self;
final $Res Function(_LegacySignUpViewState) _then;
final _SignUpState _self;
final $Res Function(_SignUpState) _then;
/// Create a copy of LegacySignUpViewState
/// Create a copy of SignUpState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? currentIndex = null,Object? firstName = null,Object? lastName = null,Object? email = null,Object? phone = null,Object? isoCode = null,Object? password = null,Object? repeatPassword = null,Object? isShowPassword = null,Object? acceptTerms = null,Object? emailError = null,Object? passwordError = null,Object? phoneError = null,Object? validationErrorKey = null,Object? apiErrorEvent = freezed,Object? isLoading = null,Object? showErrors = null,Object? isCreated = null,Object? showAccountCreated = null,}) {
return _then(_LegacySignUpViewState(
return _then(_SignUpState(
currentIndex: null == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable
as int,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable

View File

@@ -1,4 +1,4 @@
import 'package:legacy_auth/src/features/sign_up/presentation/state/sign_up_view_model.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/providers/sign_up_controller.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
@@ -17,7 +17,7 @@ class LegacyAccountCreatedScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(legacySignUpViewModelProvider);
final state = ref.watch(signUpControllerProvider);
final String email = state.email;
final String fullName = '${state.firstName} ${state.lastName}'.trim();

View File

@@ -1,17 +1,19 @@
import 'package:legacy_auth/src/features/sign_up/domain/entities/legacy_signup_error_event.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/sign_up_steps.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/state/sign_up_view_model.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/screens/account_created_screen.dart';
import 'package:legacy_auth/src/widgets/layouts/sign_up_layout.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_auth/src/core/utils/text_format_utils.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/legacy_signup_error_event.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/providers/sign_up_controller.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/screens/account_created_screen.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/sign_up_steps.dart';
import 'package:legacy_auth/src/widgets/layouts/sign_up_layout.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
String _signupApiErrorI18nKey(LegacySignupErrorEvent event) {
return switch (event) {
LegacySignupErrorEvent.emailAlreadyExists => I18n.errorEmailAlreadyRegistered,
LegacySignupErrorEvent.emailAlreadyExists =>
I18n.errorEmailAlreadyRegistered,
LegacySignupErrorEvent.invalidField => I18n.signupErrorInvalidField,
LegacySignupErrorEvent.tooManyAttempts => I18n.authErrorTooManyAttempts,
LegacySignupErrorEvent.network => I18n.authErrorNetwork,
@@ -19,71 +21,141 @@ String _signupApiErrorI18nKey(LegacySignupErrorEvent event) {
};
}
class LegacySignupScreen extends ConsumerWidget {
class LegacySignupScreen extends ConsumerStatefulWidget {
final NavigationContract navigationContract;
const LegacySignupScreen({super.key, required this.navigationContract});
Future<void> _onNextPressed(BuildContext context, WidgetRef ref) async {
FocusManager.instance.primaryFocus?.unfocus();
@override
ConsumerState<LegacySignupScreen> createState() => _LegacySignupScreenState();
}
final vm = ref.read(legacySignUpViewModelProvider.notifier);
final state = ref.read(legacySignUpViewModelProvider);
class _LegacySignupScreenState extends ConsumerState<LegacySignupScreen> {
late final SignUpFormControllers _controllers;
final steps = signUpSteps(context);
final isLastStep = state.currentIndex >= steps.length - 1;
if (!isLastStep) {
vm.next();
return;
}
final ok = await vm.signUp();
if (!context.mounted) return;
if (!ok) return;
@override
void initState() {
super.initState();
final initial = ref.read(signUpControllerProvider);
_controllers = SignUpFormControllers(
firstName: TextEditingController(text: initial.firstName),
lastName: TextEditingController(text: initial.lastName),
phone: TextEditingController(text: initial.phone),
email: TextEditingController(text: initial.email),
password: TextEditingController(text: initial.password),
repeatPassword: TextEditingController(text: initial.repeatPassword),
);
_controllers.firstName.addListener(_onFirstNameChanged);
_controllers.lastName.addListener(_onLastNameChanged);
_controllers.phone.addListener(_onPhoneChanged);
_controllers.email.addListener(_onEmailChanged);
_controllers.password.addListener(_onPasswordChanged);
_controllers.repeatPassword.addListener(_onRepeatPasswordChanged);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.read(legacySignUpViewModelProvider.notifier);
final state = ref.watch(legacySignUpViewModelProvider);
void dispose() {
_controllers.firstName.removeListener(_onFirstNameChanged);
_controllers.lastName.removeListener(_onLastNameChanged);
_controllers.phone.removeListener(_onPhoneChanged);
_controllers.email.removeListener(_onEmailChanged);
_controllers.password.removeListener(_onPasswordChanged);
_controllers.repeatPassword.removeListener(_onRepeatPasswordChanged);
_controllers.firstName.dispose();
_controllers.lastName.dispose();
_controllers.phone.dispose();
_controllers.email.dispose();
_controllers.password.dispose();
_controllers.repeatPassword.dispose();
super.dispose();
}
void _onFirstNameChanged() {
toCapitalizedController(_controllers.firstName);
ref
.read(signUpControllerProvider.notifier)
.setFirstName(_controllers.firstName.text);
}
void _onLastNameChanged() {
toCapitalizedController(_controllers.lastName);
ref
.read(signUpControllerProvider.notifier)
.setLastName(_controllers.lastName.text);
}
void _onPhoneChanged() {
ref
.read(signUpControllerProvider.notifier)
.setPhone(_controllers.phone.text);
}
void _onEmailChanged() {
ref
.read(signUpControllerProvider.notifier)
.setEmail(_controllers.email.text);
}
void _onPasswordChanged() {
ref
.read(signUpControllerProvider.notifier)
.setPassword(_controllers.password.text);
}
void _onRepeatPasswordChanged() {
ref
.read(signUpControllerProvider.notifier)
.setRepeatPassword(_controllers.repeatPassword.text);
}
Future<void> _onNextPressed() async {
FocusManager.instance.primaryFocus?.unfocus();
final notifier = ref.read(signUpControllerProvider.notifier);
final state = ref.read(signUpControllerProvider);
final steps = signUpSteps(context, _controllers);
final isLastStep = state.currentIndex >= steps.length - 1;
if (!isLastStep) {
notifier.next();
return;
}
await notifier.signUp();
}
@override
Widget build(BuildContext context) {
final state = ref.watch(signUpControllerProvider);
final notifier = ref.read(signUpControllerProvider.notifier);
ref.listen(
legacySignUpViewModelProvider.select((s) => s.validationErrorKey),
(previous, next) {
if (next.isNotEmpty) {
showTopSnackbar(
context,
message: context.translate(next),
type: MessageType.error,
);
vm.clearValidationError();
}
signUpControllerProvider.select((s) => s.validationErrorKey),
(_, next) async {
if (next.isEmpty) return;
await showErrorDialog(context, next);
notifier.clearValidationError();
},
);
ref.listen(
legacySignUpViewModelProvider.select((s) => s.apiErrorEvent),
(previous, next) {
if (next != null) {
showTopSnackbar(
context,
message: context.translate(_signupApiErrorI18nKey(next)),
type: MessageType.error,
);
vm.clearApiError();
}
signUpControllerProvider.select((s) => s.apiErrorEvent),
(_, next) async {
if (next == null) return;
await showErrorDialog(context, _signupApiErrorI18nKey(next));
notifier.clearApiError();
},
);
final steps = signUpSteps(context);
final steps = signUpSteps(context, _controllers);
final index = state.currentIndex.clamp(0, steps.length - 1);
final step = steps[index];
if (state.showAccountCreated) {
return LegacyAccountCreatedScreen(navigationContract: navigationContract);
return LegacyAccountCreatedScreen(
navigationContract: widget.navigationContract,
);
}
return LegacySignUpLayout(
supertitle: step.supertitle,
title: step.title,
@@ -93,9 +165,9 @@ class LegacySignupScreen extends ConsumerWidget {
body: step.bodyBuilder(context, ref),
isLoading: state.isLoading,
onBackPressed: state.currentIndex == 0
? navigationContract.goBack
: vm.back,
onNextPressed: () => _onNextPressed(context, ref),
? widget.navigationContract.goBack
: notifier.back,
onNextPressed: _onNextPressed,
);
}
}

View File

@@ -1,62 +1,79 @@
import 'package:country_code_picker/country_code_picker.dart';
import 'package:flutter/material.dart';
import 'package:legacy_auth/src/features/sign_up/models/sign_up_step_config.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/providers/sign_up_controller.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/screens/sign_up_password_screen.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/screens/sign_up_personal_screen.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/state/sign_up_view_model.dart';
import 'package:sf_localizations/sf_localizations.dart';
List<LegacySignUpStepConfig> signUpSteps(BuildContext context) => [
LegacySignUpStepConfig(
supertitle: context.translate(I18n.stepUserContactSupertitle),
title: context.translate(I18n.stepUserContactTitle),
subtitle: context.translate(I18n.stepUserContactSubtitle),
bodyBuilder: (context, ref) {
final vm = ref.read(legacySignUpViewModelProvider.notifier);
final state = ref.watch(legacySignUpViewModelProvider);
class SignUpFormControllers {
SignUpFormControllers({
required this.firstName,
required this.lastName,
required this.phone,
required this.email,
required this.password,
required this.repeatPassword,
});
return LegacySignupPersonalScreen(
firstNameTextFieldController: vm.firstNameController,
lastNameTextFieldController: vm.lastNameController,
phoneTextFieldController: vm.phoneController,
emailTextFieldController: vm.emailController,
final TextEditingController firstName;
final TextEditingController lastName;
final TextEditingController phone;
final TextEditingController email;
final TextEditingController password;
final TextEditingController repeatPassword;
}
acceptTerms: state.acceptTerms,
onAcceptTermsPressed: (v) => vm.setAcceptTerms(v ?? false),
termsText: context.translate(I18n.termsText),
List<LegacySignUpStepConfig> signUpSteps(
BuildContext context,
SignUpFormControllers controllers,
) =>
[
LegacySignUpStepConfig(
supertitle: context.translate(I18n.stepUserContactSupertitle),
title: context.translate(I18n.stepUserContactTitle),
subtitle: context.translate(I18n.stepUserContactSubtitle),
bodyBuilder: (context, ref) {
final notifier = ref.read(signUpControllerProvider.notifier);
final state = ref.watch(signUpControllerProvider);
firstNameLabel: context.translate(I18n.firstNameLabel),
firstNameHint: context.translate(I18n.firstNameHint),
lastNameLabel: context.translate(I18n.lastNameLabel),
lastNameHint: context.translate(I18n.lastNameHint),
phoneLabel: context.translate(I18n.phoneLabel),
phoneHint: context.translate(I18n.phoneHint),
emailLabel: context.translate(I18n.emailLabel),
emailHint: context.translate(I18n.emailHint),
isoCode: state.isoCode,
onCountryChanged: (CountryCode value) {
final code = value.code;
if (code != null) vm.updateCountry(code);
return LegacySignupPersonalScreen(
firstNameTextFieldController: controllers.firstName,
lastNameTextFieldController: controllers.lastName,
phoneTextFieldController: controllers.phone,
emailTextFieldController: controllers.email,
acceptTerms: state.acceptTerms,
onAcceptTermsPressed: (v) => notifier.setAcceptTerms(v ?? false),
termsText: context.translate(I18n.termsText),
firstNameLabel: context.translate(I18n.firstNameLabel),
firstNameHint: context.translate(I18n.firstNameHint),
lastNameLabel: context.translate(I18n.lastNameLabel),
lastNameHint: context.translate(I18n.lastNameHint),
phoneLabel: context.translate(I18n.phoneLabel),
phoneHint: context.translate(I18n.phoneHint),
emailLabel: context.translate(I18n.emailLabel),
emailHint: context.translate(I18n.emailHint),
isoCode: state.isoCode,
onCountryChanged: (CountryCode value) {
final code = value.code;
if (code != null) notifier.updateCountry(code);
},
);
},
);
},
),
LegacySignUpStepConfig(
supertitle: context.translate(I18n.stepAddressSupertitle),
title: context.translate(I18n.stepAddressTitle),
subtitle: context.translate(I18n.passwordRulesSubtitle),
bodyBuilder: (context, ref) {
final vm = ref.read(legacySignUpViewModelProvider.notifier);
final state = ref.watch(legacySignUpViewModelProvider);
),
LegacySignUpStepConfig(
supertitle: context.translate(I18n.stepAddressSupertitle),
title: context.translate(I18n.stepAddressTitle),
subtitle: context.translate(I18n.passwordRulesSubtitle),
bodyBuilder: (context, ref) {
final state = ref.watch(signUpControllerProvider);
return LegacySignUpPasswordScreen(
isPasswordVisible: state.isShowPassword,
password: state.password,
passwordTextFieldController: vm.passwordController,
repeatPasswordTextFieldController: vm.repeatPasswordController,
);
},
),
];
return LegacySignUpPasswordScreen(
isPasswordVisible: state.isShowPassword,
password: state.password,
passwordTextFieldController: controllers.password,
repeatPasswordTextFieldController: controllers.repeatPassword,
);
},
),
];

View File

@@ -1,340 +0,0 @@
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_auth/src/core/domain/repositories/sign_up_repository.dart';
import 'package:legacy_auth/src/core/providers/sign_up_repository_provider.dart';
import 'package:legacy_auth/src/core/utils/text_format_utils.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/legacy_signup_error_event.dart';
import 'package:legacy_auth/src/features/sign_up/domain/entities/sign_up_request_entity.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/mixins/sign_up_form_validation.dart';
import 'package:legacy_auth/src/features/sign_up/presentation/state/sign_up_view_state.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
final legacySignUpViewModelProvider =
NotifierProvider.autoDispose<LegacySignUpViewModel, LegacySignUpViewState>(
LegacySignUpViewModel.new,
);
class LegacySignUpViewModel extends Notifier<LegacySignUpViewState>
with SignUpFormValidation {
late final LegacySignUpRepository _repository;
late final SfTrackingRepository _tracking;
late final TextEditingController firstNameController;
late final TextEditingController lastNameController;
late final TextEditingController phoneController;
late final TextEditingController emailController;
late final TextEditingController passwordController;
late final TextEditingController repeatPasswordController;
static const int _lastIndex = 1;
@override
LegacySignUpViewState build() {
_repository = ref.read(legacySignUpRepositoryProvider);
_tracking = ref.read(sfTrackingProvider);
_initControllers();
_addListeners();
ref.onDispose(_disposeControllers);
return const LegacySignUpViewState();
}
void _initControllers() {
firstNameController = TextEditingController();
lastNameController = TextEditingController();
phoneController = TextEditingController();
emailController = TextEditingController();
passwordController = TextEditingController();
repeatPasswordController = TextEditingController();
}
void _addListeners() {
firstNameController.addListener(_onFirstNameChanged);
lastNameController.addListener(_onLastNameChanged);
phoneController.addListener(_onPhoneChanged);
emailController.addListener(_onEmailChanged);
passwordController.addListener(_onPasswordChanged);
repeatPasswordController.addListener(_onRepeatPasswordChanged);
}
void _onFirstNameChanged() {
toCapitalizedController(firstNameController);
final text = firstNameController.text;
if (text == state.firstName) return;
state = state.copyWith(firstName: text, validationErrorKey: '');
}
void _onLastNameChanged() {
toCapitalizedController(lastNameController);
final text = lastNameController.text;
if (text == state.lastName) return;
state = state.copyWith(lastName: text, validationErrorKey: '');
}
void _onPhoneChanged() {
final text = phoneController.text;
if (text == state.phone) return;
state = state.copyWith(phone: text);
if (state.showErrors) {
state = state.copyWith(phoneError: phoneErrorFor(text, state.isoCode));
}
}
void _onEmailChanged() {
final text = emailController.text;
if (text == state.email) return;
state = state.copyWith(email: text, validationErrorKey: '');
if (state.showErrors) {
state = state.copyWith(emailError: emailErrorFor(text));
}
}
void _onPasswordChanged() {
final text = passwordController.text;
if (text == state.password) return;
state = state.copyWith(password: text, validationErrorKey: '');
if (state.showErrors) {
state = state.copyWith(
passwordError: passwordErrorFor(
password: text,
repeatPassword: state.repeatPassword,
),
);
}
}
void _onRepeatPasswordChanged() {
final text = repeatPasswordController.text;
if (text == state.repeatPassword) return;
state = state.copyWith(repeatPassword: text, validationErrorKey: '');
if (state.showErrors) {
state = state.copyWith(
passwordError: passwordErrorFor(
password: state.password,
repeatPassword: text,
),
);
}
}
void updateCountry(String isoCode) {
if (isoCode == state.isoCode) return;
state = state.copyWith(isoCode: isoCode);
if (state.showErrors && state.phone.isNotEmpty) {
state = state.copyWith(
phoneError: phoneErrorFor(state.phone, isoCode),
);
}
}
void setAcceptTerms(bool value) {
if (value == state.acceptTerms) return;
state = state.copyWith(acceptTerms: value, validationErrorKey: '');
}
void toggleShowPassword() {
state = state.copyWith(isShowPassword: !state.isShowPassword);
}
void next() {
if (state.isLoading) return;
final currentStep = state.currentIndex;
final ok = switch (currentStep) {
0 => _validateStep0(),
1 => _validateStep1(),
_ => true,
};
if (!ok) {
unawaited(_tracking.legacyAuthSignupStepValidationFailed(currentStep));
return;
}
unawaited(_tracking.legacyAuthSignupStepCompleted(currentStep));
if (currentStep >= _lastIndex) {
unawaited(signUp());
return;
}
state = state.copyWith(
currentIndex: (currentStep + 1).clamp(0, _lastIndex),
);
}
void back() {
if (state.isLoading) return;
if (state.currentIndex <= 0) return;
unawaited(_tracking.legacyAuthSignupStepBack(state.currentIndex));
state = state.copyWith(
currentIndex: (state.currentIndex - 1).clamp(0, _lastIndex),
);
}
bool _validateStep0() {
final emailErr = emailErrorFor(state.email);
final phoneErr = phoneErrorFor(state.phone, state.isoCode);
state = state.copyWith(
showErrors: true,
emailError: emailErr,
phoneError: phoneErr,
validationErrorKey: '',
);
if (state.firstName.trim().isEmpty) {
state = state.copyWith(validationErrorKey: I18n.errorFirstNameRequired);
return false;
}
if (!isNameValid(state.firstName)) {
state = state.copyWith(validationErrorKey: I18n.errorNameInvalidChars);
return false;
}
if (state.lastName.trim().isEmpty) {
state = state.copyWith(validationErrorKey: I18n.errorLastNameRequired);
return false;
}
if (!isNameValid(state.lastName)) {
state = state.copyWith(validationErrorKey: I18n.errorNameInvalidChars);
return false;
}
if (phoneErr.isNotEmpty) {
state = state.copyWith(validationErrorKey: phoneErr);
return false;
}
if (emailErr.isNotEmpty) {
state = state.copyWith(validationErrorKey: emailErr);
return false;
}
if (!state.acceptTerms) {
state = state.copyWith(validationErrorKey: I18n.errorAcceptTerms);
return false;
}
return true;
}
bool _validateStep1() {
final passwordErr = passwordErrorFor(
password: state.password,
repeatPassword: state.repeatPassword,
);
state = state.copyWith(
showErrors: true,
passwordError: passwordErr,
validationErrorKey: passwordErr,
);
return passwordErr.isEmpty;
}
bool _validateForm() => _validateStep0() && _validateStep1();
Future<bool> signUp() async {
if (state.isLoading) return false;
if (!_validateForm()) return false;
final parsedPhone = SfPhoneNumber.tryParse(
state.phone,
defaultIsoCode: state.isoCode,
);
if (parsedPhone == null) {
state = state.copyWith(
validationErrorKey: I18n.errorMessagePhoneIsInvalid,
phoneError: I18n.errorMessagePhoneIsInvalid,
);
return false;
}
state = state.copyWith(
isLoading: true,
validationErrorKey: '',
apiErrorEvent: null,
showAccountCreated: false,
);
unawaited(_tracking.legacyAuthSignupStarted());
try {
final request = LegacySignUpRequestEntity(
firstName: state.firstName.trim().toUpperCase(),
lastName: state.lastName.trim().toUpperCase(),
email: state.email.trim(),
phone: parsedPhone.e164,
language: _deviceLanguage(),
password: state.password,
);
final response = await _repository.signUp(request: request);
if (!ref.mounted) return false;
unawaited(_tracking.legacyAuthSignupCompleted());
state = state.copyWith(
isLoading: false,
isCreated: response.isCreated,
showAccountCreated: true,
);
return true;
} catch (e) {
if (!ref.mounted) return false;
unawaited(_tracking.legacyAuthSignupFailed(formatErrorMessage(e)));
state = state.copyWith(
isLoading: false,
apiErrorEvent: mapSignupError(e),
);
return false;
}
}
void clearApiError() {
if (state.apiErrorEvent != null) state = state.copyWith(apiErrorEvent: null);
}
void clearValidationError() {
if (state.validationErrorKey.isNotEmpty) {
state = state.copyWith(validationErrorKey: '');
}
}
String _deviceLanguage() {
final code = PlatformDispatcher.instance.locale.languageCode
.trim()
.toLowerCase();
return code.isEmpty ? 'es' : code;
}
void _disposeControllers() {
firstNameController.removeListener(_onFirstNameChanged);
lastNameController.removeListener(_onLastNameChanged);
phoneController.removeListener(_onPhoneChanged);
emailController.removeListener(_onEmailChanged);
passwordController.removeListener(_onPasswordChanged);
repeatPasswordController.removeListener(_onRepeatPasswordChanged);
firstNameController.dispose();
lastNameController.dispose();
phoneController.dispose();
emailController.dispose();
passwordController.dispose();
repeatPasswordController.dispose();
}
}