From c23ae39b875e597da8e5266aca75fb13815b8634 Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Wed, 22 Apr 2026 23:29:38 +0200 Subject: [PATCH] refactor(legacy_auth): migrate sign_up to Riverpod --- .../providers/sign_up_controller.dart | 278 ++++++++++++++ .../providers/sign_up_controller.g.dart | 63 ++++ .../sign_up_state.dart} | 12 +- .../sign_up_state.freezed.dart} | 80 ++--- .../screens/account_created_screen.dart | 4 +- .../sign_up/presentation/sign_up_screen.dart | 176 ++++++--- .../sign_up/presentation/sign_up_steps.dart | 117 +++--- .../state/sign_up_view_model.dart | 340 ------------------ 8 files changed, 578 insertions(+), 492 deletions(-) create mode 100644 modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.dart create mode 100644 modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.g.dart rename modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/{state/sign_up_view_state.dart => providers/sign_up_state.dart} (82%) rename modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/{state/sign_up_view_state.freezed.dart => providers/sign_up_state.freezed.dart} (67%) delete mode 100644 modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.dart new file mode 100644 index 00000000..85ab0365 --- /dev/null +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.dart @@ -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 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; + } +} diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.g.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.g.dart new file mode 100644 index 00000000..d74f1f55 --- /dev/null +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_controller.g.dart @@ -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 { + 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(value), + ); + } +} + +String _$signUpControllerHash() => r'bbc80db2b0e7644b0a8764b77cac9d12eebb2372'; + +abstract class _$SignUpController extends $Notifier { + SignUpState build(); + @$mustCallSuper + @override + void runBuild() { + final created = build(); + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + SignUpState, + Object?, + Object? + >; + element.handleValue(ref, created); + } +} diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.dart similarity index 82% rename from modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.dart rename to modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.dart index 3714254c..f660aeab 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.dart @@ -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; } diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.freezed.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.freezed.dart similarity index 67% rename from modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.freezed.dart rename to modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.freezed.dart index 5b8da39a..210ac8f3 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_state.freezed.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/providers/sign_up_state.freezed.dart @@ -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 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 get copyWith => _$LegacySignUpViewStateCopyWithImpl(this as LegacySignUpViewState, _$identity); +$SignUpStateCopyWith get copyWith => _$SignUpStateCopyWithImpl(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 Function( _LegacySignUpViewState value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(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 Function( _LegacySignUpViewState value) $default,){ +@optionalTypeArgs TResult map(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? Function( _LegacySignUpViewState value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(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 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 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? 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 diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/screens/account_created_screen.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/screens/account_created_screen.dart index c3b41a7c..afdbd63d 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/screens/account_created_screen.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/screens/account_created_screen.dart @@ -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(); diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_screen.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_screen.dart index ac54c3e2..b6fa8bf1 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_screen.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_screen.dart @@ -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 _onNextPressed(BuildContext context, WidgetRef ref) async { - FocusManager.instance.primaryFocus?.unfocus(); + @override + ConsumerState createState() => _LegacySignupScreenState(); +} - final vm = ref.read(legacySignUpViewModelProvider.notifier); - final state = ref.read(legacySignUpViewModelProvider); +class _LegacySignupScreenState extends ConsumerState { + 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 _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, ); } } diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_steps.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_steps.dart index f21344f7..68fb826b 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_steps.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/sign_up_steps.dart @@ -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 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 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, + ); + }, + ), + ]; diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart b/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart deleted file mode 100644 index 284e27fe..00000000 --- a/modules/legacy/modules/legacy_auth/lib/src/features/sign_up/presentation/state/sign_up_view_model.dart +++ /dev/null @@ -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.new, - ); - -class LegacySignUpViewModel extends Notifier - 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 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(); - } -}