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

@@ -168,6 +168,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
country_code_picker:
dependency: transitive
description:
name: country_code_picker
sha256: f0411f4833b6f98e8b7215f4fa3813bcc88e50f13925f70a170dbd36e3e447f5
url: "https://pub.dev"
source: hosted
version: "3.4.1"
coverage: coverage:
dependency: transitive dependency: transitive
description: description:
@@ -214,6 +222,14 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" version: "0.0.1"
diacritic:
dependency: transitive
description:
name: diacritic
sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
dio: dio:
dependency: transitive dependency: transitive
description: description:
@@ -432,6 +448,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.20.2"
intl_phone_field_v2:
dependency: transitive
description:
name: intl_phone_field_v2
sha256: b1e5077e31cc8705639a69b2e0410a8ecc858c3e518726d99b378b6c35adfefb
url: "https://pub.dev"
source: hosted
version: "4.0.5"
io: io:
dependency: transitive dependency: transitive
description: description:

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$LinkPhoneViewState { 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 /// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $LinkPhoneViewStateCopyWith<LinkPhoneViewState> get copyWith => _$LinkPhoneViewS
@override @override
bool operator ==(Object other) { 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 @override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested); int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
@override @override
String toString() { 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; factory $LinkPhoneViewStateCopyWith(LinkPhoneViewState value, $Res Function(LinkPhoneViewState) _then) = _$LinkPhoneViewStateCopyWithImpl;
@useResult @useResult
$Res call({ $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 /// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values. /// 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( return _then(_self.copyWith(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable 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,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // 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,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
as bool, 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) { switch (_that) {
case _LinkPhoneViewState() when $default != null: 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(); 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) { switch (_that) {
case _LinkPhoneViewState(): 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'); 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) { switch (_that) {
case _LinkPhoneViewState() when $default != null: 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; return null;
} }
@@ -209,11 +210,12 @@ return $default(_that.phoneNumber,_that.errorMessage,_that.isLoading,_that.codeR
class _LinkPhoneViewState implements LinkPhoneViewState { 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@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 isLoading;
@override@JsonKey() final bool codeRequested; @override@JsonKey() final bool codeRequested;
@@ -227,16 +229,16 @@ _$LinkPhoneViewStateCopyWith<_LinkPhoneViewState> get copyWith => __$LinkPhoneVi
@override @override
bool operator ==(Object other) { 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 @override
int get hashCode => Object.hash(runtimeType,phoneNumber,errorMessage,isLoading,codeRequested); int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
@override @override
String toString() { 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; factory _$LinkPhoneViewStateCopyWith(_LinkPhoneViewState value, $Res Function(_LinkPhoneViewState) _then) = __$LinkPhoneViewStateCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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 /// Create a copy of LinkPhoneViewState
/// with the given fields replaced by the non-null parameter values. /// 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( return _then(_LinkPhoneViewState(
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable 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,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // 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,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
as bool, as bool,
)); ));

View File

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

View File

@@ -9,4 +9,5 @@ export 'src/snackbars/snackbar.dart';
export 'src/buttons/primary_button.dart'; export 'src/buttons/primary_button.dart';
export 'src/buttons/secondary_button.dart'; export 'src/buttons/secondary_button.dart';
export 'src/buttons/custom_text_button.dart'; export 'src/buttons/custom_text_button.dart';
export 'src/dropdowns/dropdown.dart'; export 'src/dropdowns/dropdown.dart';
export 'src/dropdowns/country_prefix_picker.dart';

View File

@@ -0,0 +1,79 @@
import 'package:country_code_picker/country_code_picker.dart';
import 'package:flutter/material.dart';
class CountryPrefixPicker extends StatelessWidget {
const CountryPrefixPicker({
super.key,
required this.onChanged,
this.initialCountryCode = '+34',
this.radius = 12,
this.width = 90,
this.height = 55,
this.borderColor = const Color(0xFF4B4B4B),
this.backgroundColor = Colors.white,
});
final ValueChanged<CountryCode> onChanged;
final String initialCountryCode;
final double radius;
final double width;
final double height;
final Color borderColor;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: CountryCodePicker(
onChanged: onChanged,
initialSelection: initialCountryCode,
showFlag: false,
showDropDownButton: false,
hideMainText: true,
padding: EdgeInsets.zero,
builder: (CountryCode? country) {
if (country == null) {
return const SizedBox.shrink();
}
return InputDecorator(
decoration: InputDecoration(
isDense: false,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
),
filled: true,
fillColor: backgroundColor,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
borderSide: BorderSide(color: borderColor),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
borderSide: BorderSide(color: borderColor),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (country.flagUri != null)
Image.asset(
country.flagUri!,
package: 'country_code_picker',
width: 24,
height: 24,
fit: BoxFit.cover,
),
const Icon(Icons.arrow_drop_down, size: 24),
],
),
);
},
),
);
}
}

View File

@@ -1,18 +1,17 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CustomTextField extends StatefulWidget{ class CustomTextField extends StatefulWidget {
bool? showPassword; final bool? showPassword;
final bool numeric; final bool numeric;
final String hint; final String hint;
final String label; final String label;
final int? lines; final int? lines;
final ValueChanged<String>? onChanged; final ValueChanged<String>? onChanged;
final int? length; final int? length;
final TextEditingController? controller;
CustomTextField({ const CustomTextField({
super.key, super.key,
this.showPassword, this.showPassword,
this.numeric = false, this.numeric = false,
@@ -21,63 +20,74 @@ class CustomTextField extends StatefulWidget{
this.lines, this.lines,
this.length, this.length,
this.onChanged, this.onChanged,
this.controller,
}); });
@override @override
State<CustomTextField> createState() => CustomTextFieldState(); State<CustomTextField> createState() => CustomTextFieldState();
} }
class CustomTextFieldState extends State<CustomTextField>{ class CustomTextFieldState extends State<CustomTextField> {
late bool _showPassword;
@override
void initState() {
super.initState();
_showPassword = widget.showPassword ?? true;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
spacing: 8, spacing: 8,
children: [ children: [
?widget.label == '' ? null : Align( if (widget.label.isNotEmpty)
alignment: Alignment.bottomLeft, Align(
child: Text( alignment: Alignment.bottomLeft,
widget.label, child: Text(
style: TextStyle(fontSize: 14, letterSpacing: 0), widget.label,
) style: const TextStyle(fontSize: 14, letterSpacing: 0),
), ),
),
TextFormField( TextFormField(
keyboardType: widget.numeric? TextInputType.number : TextInputType.text, controller: widget.controller,
obscureText: !(widget.showPassword ?? true), keyboardType: widget.numeric
enableSuggestions: widget.showPassword ?? true, ? TextInputType.number
autocorrect: !(widget.showPassword ?? false), : TextInputType.text,
style: TextStyle(color: Color(0xFF4B4B4B)), obscureText: !_showPassword,
inputFormatters: widget.numeric? [ enableSuggestions: _showPassword,
FilteringTextInputFormatter.digitsOnly autocorrect: !_showPassword,
] : [], style: const TextStyle(color: Color(0xFF4B4B4B)),
inputFormatters: widget.numeric
? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly]
: const <TextInputFormatter>[],
decoration: InputDecoration( decoration: InputDecoration(
counterText: "", counterText: "",
hintText: widget.hint, hintText: widget.hint,
//labelText: widget.label, border: const OutlineInputBorder(
//floatingLabelBehavior: FloatingLabelBehavior.always,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)), borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Color(0xFF4B4B4B)), borderSide: BorderSide(color: Color(0xFF4B4B4B)),
gapPadding: 16 gapPadding: 16,
), ),
suffixIcon: widget.showPassword!=null ? IconButton( suffixIcon: widget.showPassword != null
icon: Icon(widget.showPassword! ? IconButton(
? Icons.visibility_off icon: Icon(
: Icons.visibility), _showPassword ? Icons.visibility_off : Icons.visibility,
onPressed: () { ),
setState(() { onPressed: () {
widget.showPassword = !widget.showPassword!; setState(() {
}); _showPassword = !_showPassword;
}, });
) : null, },
)
: null,
), ),
minLines: widget.lines ?? 1, minLines: widget.lines ?? 1,
maxLines: widget.lines ?? 1, maxLines: widget.lines ?? 1,
maxLength: widget.length, maxLength: widget.length,
onChanged: widget.onChanged ?? (_)=>{}, onChanged: widget.onChanged,
) ),
], ],
); );
} }
} }

View File

@@ -16,6 +16,7 @@ dependencies:
path: ../utils path: ../utils
flutter_riverpod: ^3.0.3 flutter_riverpod: ^3.0.3
get_it: ^9.0.5 get_it: ^9.0.5
country_code_picker: ^3.4.1
fonts: fonts:
path: ../../packages/fonts path: ../../packages/fonts