added country_code_picker package to link phone feature

This commit is contained in:
AlcalaJulian
2025-12-04 17:32:42 +01:00
parent 6b3776f618
commit ad10ad3b59
10 changed files with 221 additions and 88 deletions

View File

@@ -1,4 +1,5 @@
import 'package:auth/src/features/link_phone/presentation/link_phone_view_model.dart';
import 'package:design_system/src/dropdowns/country_prefix_picker.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -10,13 +11,11 @@ class LinkPhoneScreen extends ConsumerWidget {
const LinkPhoneScreen({super.key, required this.navigationContract});
void _onCountryChanged(int? value) {}
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final viewModel = ref.watch(linkPhoneViewModelProvider.notifier);
final viewModel = ref.read(linkPhoneViewModelProvider.notifier);
final viewState = ref.watch(linkPhoneViewModelProvider);
return Scaffold(
@@ -56,19 +55,17 @@ class LinkPhoneScreen extends ConsumerWidget {
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: const [
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
Icon(Icons.outlined_flag),
],
onChanged: _onCountryChanged,
width: 80,
CountryPrefixPicker(
initialCountryCode: viewState.dialCode,
onChanged: (country) {
viewModel.updateDialCode(
country.dialCode ?? viewState.dialCode,
);
},
),
Expanded(
child: CustomTextField(
// controller: viewModel.phoneNumberController,
controller: viewModel.phoneNumberController,
hint: context.translate(I18n.phoneNumber),
numeric: true,
),
@@ -80,11 +77,10 @@ class LinkPhoneScreen extends ConsumerWidget {
const SizedBox(height: 16),
if (viewState.errorMessage?.isNotEmpty ?? false) ...[
if (viewState.errorMessage.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
// context.translate(viewState.errorMessage
'',
viewState.errorMessage,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color.fromRGBO(239, 17, 17, 1),
@@ -99,7 +95,7 @@ class LinkPhoneScreen extends ConsumerWidget {
onPressed: () async {
await viewModel.requestCode();
final updatedState = ref.read(linkPhoneViewModelProvider);
if (updatedState.errorMessage!.isEmpty) {
if (updatedState.errorMessage.isEmpty) {
navigationContract.pushTo(AppRoutes.phoneCode);
}
},

View File

@@ -17,30 +17,42 @@ class LinkPhoneViewModel extends Notifier<LinkPhoneViewState> {
@override
LinkPhoneViewState build() {
_linkPhoneUseCase = ref.read(linkPhoneUseCaseProvider);
phoneNumberController = TextEditingController();
phoneNumberController.addListener(_onPhoneNumberChanged);
ref.onDispose(disposeControllers);
return const LinkPhoneViewState();
}
void _onPhoneNumberChanged() {
final raw = phoneNumberController.text;
state = state.copyWith(phoneNumber: raw, errorMessage: '');
}
Future<void> requestCode() async {
final phone = phoneNumberController.text.trim();
void updateDialCode(String dialCode) {
state = state.copyWith(dialCode: dialCode, errorMessage: '');
}
if (phone.isEmpty) {
Future<void> requestCode() async {
final trimmedNumber = state.phoneNumber.trim();
if (trimmedNumber.isEmpty) {
state = state.copyWith(errorMessage: 'El teléfono no puede estar vacío');
return;
}
state = state.copyWith(isLoading: true, errorMessage: '');
final fullPhone = '${state.dialCode}$trimmedNumber';
state = state.copyWith(
isLoading: true,
errorMessage: '',
codeRequested: false,
);
try {
await _linkPhoneUseCase.requestCode(phone: phone);
await _linkPhoneUseCase.requestCode(phone: fullPhone);
if (!ref.mounted) return;
state = state.copyWith(
@@ -50,7 +62,12 @@ class LinkPhoneViewModel extends Notifier<LinkPhoneViewState> {
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: e.toString());
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
codeRequested: false,
);
}
}

View File

@@ -6,7 +6,8 @@ part 'link_phone_view_state.freezed.dart';
abstract class LinkPhoneViewState with _$LinkPhoneViewState {
const factory LinkPhoneViewState({
@Default('') String phoneNumber,
String? errorMessage,
@Default('+34') String dialCode,
@Default('') String errorMessage,
@Default(false) bool isLoading,
@Default(false) bool codeRequested,
}) = _LinkPhoneViewState;

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LinkPhoneViewState {
String get phoneNumber; String? get errorMessage; bool get isLoading; bool get codeRequested;
String get phoneNumber; String get dialCode; String get errorMessage; bool get isLoading; bool get codeRequested;
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $LinkPhoneViewStateCopyWith<LinkPhoneViewState> get copyWith => _$LinkPhoneViewS
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
return identical(this, other) || (other.runtimeType == runtimeType&&other is LinkPhoneViewState&&(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));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested);
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
@override
String toString() {
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
}
@@ -45,7 +45,7 @@ abstract mixin class $LinkPhoneViewStateCopyWith<$Res> {
factory $LinkPhoneViewStateCopyWith(LinkPhoneViewState value, $Res Function(LinkPhoneViewState) _then) = _$LinkPhoneViewStateCopyWithImpl;
@useResult
$Res call({
String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested
});
@@ -62,11 +62,12 @@ class _$LinkPhoneViewStateCopyWithImpl<$Res>
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? errorMessage = freezed,Object? isLoading = null,Object? codeRequested = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,}) {
return _then(_self.copyWith(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,errorMessage: freezed == 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 String,dialCode: null == dialCode ? _self.dialCode : dialCode // 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
as bool,
));
@@ -153,10 +154,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return orElse();
}
@@ -174,10 +175,10 @@ return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeR
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested) $default,) {final _that = this;
switch (_that) {
case _LinkPhoneViewState():
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
throw StateError('Unexpected subclass');
}
@@ -194,10 +195,10 @@ return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeR
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested)? $default,) {final _that = this;
switch (_that) {
case _LinkPhoneViewState() when $default != null:
return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
return null;
}
@@ -209,11 +210,12 @@ return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeR
class _LinkPhoneViewState implements LinkPhoneViewState {
const _LinkPhoneViewState({this.phoneNumber = '', this.errorMessage, this.isLoading = false, this.codeRequested = false});
const _LinkPhoneViewState({this.phoneNumber = '', this.dialCode = '+34', this.errorMessage = '', this.isLoading = false, this.codeRequested = false});
@override@JsonKey() final String phoneNumber;
@override final String? errorMessage;
@override@JsonKey() final String dialCode;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool codeRequested;
@@ -227,16 +229,16 @@ _$LinkPhoneViewStateCopyWith<_LinkPhoneViewState> get copyWith => __$LinkPhoneVi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LinkPhoneViewState&&(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));
}
@override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested);
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
@override
String toString() {
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
}
@@ -247,7 +249,7 @@ abstract mixin class _$LinkPhoneViewStateCopyWith<$Res> implements $LinkPhoneVie
factory _$LinkPhoneViewStateCopyWith(_LinkPhoneViewState value, $Res Function(_LinkPhoneViewState) _then) = __$LinkPhoneViewStateCopyWithImpl;
@override @useResult
$Res call({
String phoneNumber, String? errorMessage, bool isLoading, bool codeRequested
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested
});
@@ -264,11 +266,12 @@ class __$LinkPhoneViewStateCopyWithImpl<$Res>
/// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? errorMessage = freezed,Object? isLoading = null,Object? codeRequested = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,}) {
return _then(_LinkPhoneViewState(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
as String,errorMessage: freezed == 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 String,dialCode: null == dialCode ? _self.dialCode : dialCode // 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
as bool,
));

View File

@@ -32,6 +32,7 @@ dependencies:
freezed_annotation: ^3.1.0
freezed: ^3.2.3
dio: ^5.9.0
country_code_picker: ^3.4.1
dev_dependencies:
flutter_test: