refactor(legacy-auth): migrate link_phone to SfPhoneNumber with E.164

This commit is contained in:
2026-04-15 17:05:50 +02:00
parent 08e099fc37
commit 85be483c4e
12 changed files with 85 additions and 72 deletions

View File

@@ -59,11 +59,10 @@ class LegacyRequestLinkPhoneScreen extends ConsumerWidget {
children: [
CountryPrefixPicker(
headerText: context.translate(I18n.selectYourCountry),
initialSelection: viewState.dialCode,
initialSelection: viewState.isoCode,
onChanged: (country) {
viewModel.updateDialCode(
country.dialCode ?? viewState.dialCode,
);
final code = country.code;
if (code != null) viewModel.updateCountry(code);
},
),
Expanded(
@@ -83,7 +82,7 @@ class LegacyRequestLinkPhoneScreen extends ConsumerWidget {
if (viewState.errorMessage.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
viewState.errorMessage,
context.translate(viewState.errorMessage),
textAlign: TextAlign.center,
style: const TextStyle(
color: Color.fromRGBO(239, 17, 17, 1),

View File

@@ -1,12 +1,13 @@
import 'dart:async';
import 'package:legacy_auth/src/features/link_phone/presentation/providers/link_phone_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_tracking/sf_tracking.dart';
import 'package:legacy_auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
import 'package:legacy_auth/src/features/link_phone/presentation/providers/link_phone_provider.dart';
import 'package:legacy_auth/src/features/link_phone/presentation/state/link_phone_view_state.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
final legacyLinkPhoneViewModelProvider =
NotifierProvider.autoDispose<
@@ -36,17 +37,17 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
}
void _onPhoneNumberChanged() {
final raw = phoneNumberController.text;
state = state.copyWith(
phoneNumber: raw,
phoneNumber: phoneNumberController.text,
errorMessage: '',
codeVerified: false,
);
}
void updateDialCode(String dialCode) {
void updateCountry(String isoCode) {
if (isoCode == state.isoCode) return;
state = state.copyWith(
dialCode: dialCode,
isoCode: isoCode,
errorMessage: '',
codeVerified: false,
);
@@ -57,18 +58,33 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
state = state.copyWith(errorMessage: '', codeVerified: false);
}
Future<void> requestCode() async {
final trimmedNumber = state.phoneNumber.trim();
if (trimmedNumber.isEmpty) {
SfPhoneNumber? _parsePhone() {
if (state.phoneNumber.trim().isEmpty) {
state = state.copyWith(
errorMessage: 'errorMessagePhoneIsEmpty',
errorMessage: I18n.errorMessagePhoneIsEmpty,
codeVerified: false,
);
return;
return null;
}
final fullPhone = '${state.dialCode}$trimmedNumber';
final parsed = SfPhoneNumber.tryParse(
state.phoneNumber,
defaultIsoCode: state.isoCode,
);
if (parsed == null) {
state = state.copyWith(
errorMessage: I18n.errorMessagePhoneIsInvalid,
codeVerified: false,
);
return null;
}
return parsed;
}
Future<void> requestCode() async {
final parsed = _parsePhone();
if (parsed == null) return;
state = state.copyWith(
isLoading: true,
@@ -78,15 +94,15 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
);
try {
await _linkPhoneUseCase.requestCode(phone: fullPhone);
await _linkPhoneUseCase.requestCode(phone: parsed.e164);
if (!ref.mounted) return;
unawaited(_tracking.legacyAuthLinkPhoneCodeRequested());
state = state.copyWith(
isLoading: false,
errorMessage: '',
codeRequested: true,
sentTo: parsed.format(),
);
} catch (e) {
if (!ref.mounted) return;
@@ -95,7 +111,7 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
errorMessage: I18n.errorGeneric,
codeRequested: false,
codeVerified: false,
);
@@ -103,22 +119,13 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
}
Future<void> verifyCode() async {
final dialCode = state.dialCode;
final phoneNumber = state.phoneNumber.trim();
final parsed = _parsePhone();
if (parsed == null) return;
final code = codeController.text.trim();
final fullPhone = '$dialCode$phoneNumber';
if (phoneNumber.isEmpty) {
state = state.copyWith(
errorMessage: 'errorMessagePhoneIsEmpty',
codeVerified: false,
);
return;
}
if (code.isEmpty) {
state = state.copyWith(
errorMessage: 'errorMessageCodeIsEmpty',
errorMessage: I18n.errorMessageCodeIsEmpty,
codeVerified: false,
);
return;
@@ -131,16 +138,12 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
);
try {
await _linkPhoneUseCase.verifyCode(phone: fullPhone, code: code);
await _linkPhoneUseCase.verifyCode(phone: parsed.e164, code: code);
if (!ref.mounted) return;
unawaited(_tracking.legacyAuthLinkPhoneCodeVerified());
state = state.copyWith(
isLoading: false,
errorMessage: '',
codeVerified: true,
);
state = state.copyWith(isLoading: false, codeVerified: true);
} catch (e) {
if (!ref.mounted) return;
@@ -150,7 +153,7 @@ class LegacyLinkPhoneViewModel extends Notifier<LegacyLinkPhoneViewState> {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
errorMessage: I18n.errorGeneric,
codeVerified: false,
);
}

View File

@@ -6,7 +6,8 @@ part 'link_phone_view_state.freezed.dart';
abstract class LegacyLinkPhoneViewState with _$LegacyLinkPhoneViewState {
const factory LegacyLinkPhoneViewState({
@Default('') String phoneNumber,
@Default('+34') String dialCode,
@Default('ES') String isoCode,
@Default('') String sentTo,
@Default('') String errorMessage,
@Default(false) bool isLoading,
@Default(false) bool codeRequested,

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LegacyLinkPhoneViewState {
String get phoneNumber; String get dialCode; String get errorMessage; bool get isLoading; bool get codeRequested; bool get codeVerified;
String get phoneNumber; String get isoCode; String get sentTo; String get errorMessage; bool get isLoading; bool get codeRequested; bool get codeVerified;
/// Create a copy of LegacyLinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $LegacyLinkPhoneViewStateCopyWith<LegacyLinkPhoneViewState> get copyWith => _$Le
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacyLinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested)&&(identical(other.codeVerified, codeVerified) || other.codeVerified == codeVerified));
return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacyLinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.sentTo, sentTo) || other.sentTo == sentTo)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested)&&(identical(other.codeVerified, codeVerified) || other.codeVerified == codeVerified));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested,codeVerified);
int get hashCode => Object.hash(runtimeType,phoneNumber,isoCode,sentTo,errorMessage,isLoading,codeRequested,codeVerified);
@override
String toString() {
return 'LegacyLinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested, codeVerified: $codeVerified)';
return 'LegacyLinkPhoneViewState(phoneNumber: $phoneNumber, isoCode: $isoCode, sentTo: $sentTo, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested, codeVerified: $codeVerified)';
}
@@ -45,7 +45,7 @@ abstract mixin class $LegacyLinkPhoneViewStateCopyWith<$Res> {
factory $LegacyLinkPhoneViewStateCopyWith(LegacyLinkPhoneViewState value, $Res Function(LegacyLinkPhoneViewState) _then) = _$LegacyLinkPhoneViewStateCopyWithImpl;
@useResult
$Res call({
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified
String phoneNumber, String isoCode, String sentTo, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified
});
@@ -62,10 +62,11 @@ class _$LegacyLinkPhoneViewStateCopyWithImpl<$Res>
/// Create a copy of LegacyLinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,Object? codeVerified = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? isoCode = null,Object? sentTo = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,Object? codeVerified = null,}) {
return _then(_self.copyWith(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,isoCode: null == isoCode ? _self.isoCode : isoCode // ignore: cast_nullable_to_non_nullable
as String,sentTo: null == sentTo ? _self.sentTo : sentTo // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
@@ -155,10 +156,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phoneNumber, String isoCode, String sentTo, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LegacyLinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
return $default(_that.phoneNumber,_that.isoCode,_that.sentTo,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
return orElse();
}
@@ -176,10 +177,10 @@ return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoad
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phoneNumber, String isoCode, String sentTo, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified) $default,) {final _that = this;
switch (_that) {
case _LegacyLinkPhoneViewState():
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
return $default(_that.phoneNumber,_that.isoCode,_that.sentTo,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
throw StateError('Unexpected subclass');
}
@@ -196,10 +197,10 @@ return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoad
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phoneNumber, String isoCode, String sentTo, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified)? $default,) {final _that = this;
switch (_that) {
case _LegacyLinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
return $default(_that.phoneNumber,_that.isoCode,_that.sentTo,_that.errorMessage,_that.isLoading,_that.codeRequested,_that.codeVerified);case _:
return null;
}
@@ -211,11 +212,12 @@ return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoad
class _LegacyLinkPhoneViewState implements LegacyLinkPhoneViewState {
const _LegacyLinkPhoneViewState({this.phoneNumber = '', this.dialCode = '+34', this.errorMessage = '', this.isLoading = false, this.codeRequested = false, this.codeVerified = false});
const _LegacyLinkPhoneViewState({this.phoneNumber = '', this.isoCode = 'ES', this.sentTo = '', this.errorMessage = '', this.isLoading = false, this.codeRequested = false, this.codeVerified = false});
@override@JsonKey() final String phoneNumber;
@override@JsonKey() final String dialCode;
@override@JsonKey() final String isoCode;
@override@JsonKey() final String sentTo;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool codeRequested;
@@ -231,16 +233,16 @@ _$LegacyLinkPhoneViewStateCopyWith<_LegacyLinkPhoneViewState> get copyWith => __
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacyLinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested)&&(identical(other.codeVerified, codeVerified) || other.codeVerified == codeVerified));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacyLinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.sentTo, sentTo) || other.sentTo == sentTo)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested)&&(identical(other.codeVerified, codeVerified) || other.codeVerified == codeVerified));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested,codeVerified);
int get hashCode => Object.hash(runtimeType,phoneNumber,isoCode,sentTo,errorMessage,isLoading,codeRequested,codeVerified);
@override
String toString() {
return 'LegacyLinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested, codeVerified: $codeVerified)';
return 'LegacyLinkPhoneViewState(phoneNumber: $phoneNumber, isoCode: $isoCode, sentTo: $sentTo, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested, codeVerified: $codeVerified)';
}
@@ -251,7 +253,7 @@ abstract mixin class _$LegacyLinkPhoneViewStateCopyWith<$Res> implements $Legacy
factory _$LegacyLinkPhoneViewStateCopyWith(_LegacyLinkPhoneViewState value, $Res Function(_LegacyLinkPhoneViewState) _then) = __$LegacyLinkPhoneViewStateCopyWithImpl;
@override @useResult
$Res call({
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified
String phoneNumber, String isoCode, String sentTo, String errorMessage, bool isLoading, bool codeRequested, bool codeVerified
});
@@ -268,10 +270,11 @@ class __$LegacyLinkPhoneViewStateCopyWithImpl<$Res>
/// Create a copy of LegacyLinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,Object? codeVerified = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? isoCode = null,Object? sentTo = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,Object? codeVerified = null,}) {
return _then(_LegacyLinkPhoneViewState(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,isoCode: null == isoCode ? _self.isoCode : isoCode // ignore: cast_nullable_to_non_nullable
as String,sentTo: null == sentTo ? _self.sentTo : sentTo // ignore: cast_nullable_to_non_nullable
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable

View File

@@ -42,7 +42,7 @@ class LegacyVerifyLinkPhoneCodeScreen extends ConsumerWidget {
text: context.translate(I18n.verificationCodeSentTo),
children: [
TextSpan(
text: '${viewState.dialCode}${viewState.phoneNumber}',
text: viewState.sentTo,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
@@ -67,7 +67,7 @@ class LegacyVerifyLinkPhoneCodeScreen extends ConsumerWidget {
if (viewState.errorMessage.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
viewState.errorMessage,
context.translate(viewState.errorMessage),
textAlign: TextAlign.center,
style: const TextStyle(
color: Color.fromRGBO(239, 17, 17, 1),

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Später",
"appUpdateNow": "Jetzt aktualisieren",
"contactsPermissionBlocked": "Aktiviere die Kontakte-Berechtigung in den Systemeinstellungen, um zu importieren.",
"openSettings": "Einstellungen öffnen"
"openSettings": "Einstellungen öffnen",
"errorMessageCodeIsEmpty": "Der Code darf nicht leer sein"
}

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Later",
"appUpdateNow": "Update now",
"contactsPermissionBlocked": "Enable the contacts permission in system settings to import.",
"openSettings": "Open settings"
"openSettings": "Open settings",
"errorMessageCodeIsEmpty": "The code cannot be empty"
}

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Más tarde",
"appUpdateNow": "Actualizar ahora",
"contactsPermissionBlocked": "Activa el permiso de contactos en los ajustes del sistema para importar.",
"openSettings": "Abrir ajustes"
"openSettings": "Abrir ajustes",
"errorMessageCodeIsEmpty": "El código no puede estar vacío"
}

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Plus tard",
"appUpdateNow": "Mettre à jour maintenant",
"contactsPermissionBlocked": "Active la permission des contacts dans les paramètres pour importer.",
"openSettings": "Ouvrir les paramètres"
"openSettings": "Ouvrir les paramètres",
"errorMessageCodeIsEmpty": "Le code ne peut pas être vide"
}

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Più tardi",
"appUpdateNow": "Aggiorna ora",
"contactsPermissionBlocked": "Attiva il permesso dei contatti nelle impostazioni per importare.",
"openSettings": "Apri impostazioni"
"openSettings": "Apri impostazioni",
"errorMessageCodeIsEmpty": "Il codice non può essere vuoto"
}

View File

@@ -865,5 +865,6 @@
"appUpdateLater": "Mais tarde",
"appUpdateNow": "Atualizar agora",
"contactsPermissionBlocked": "Ativa a permissão de contactos nas definições para importar.",
"openSettings": "Abrir definições"
"openSettings": "Abrir definições",
"errorMessageCodeIsEmpty": "O código não pode estar vazio"
}

View File

@@ -370,6 +370,7 @@ class I18n {
static const String errorMessagePasswordTooShort =
'errorMessagePasswordTooShort';
static const String errorMessagePhoneIsEmpty = 'errorMessagePhoneIsEmpty';
static const String errorMessageCodeIsEmpty = 'errorMessageCodeIsEmpty';
static const String contactsPermissionBlocked = 'contactsPermissionBlocked';
static const String openSettings = 'openSettings';
static const String errorMessagePhoneIsInvalid = 'errorMessagePhoneIsInvalid';