Skip to content

Commit

Permalink
Merge pull request #150 from Bdaya-Dev/fix/phone-field
Browse files Browse the repository at this point in the history
Fix reactive_phone_form_field
  • Loading branch information
vasilich6107 authored May 2, 2024
2 parents ac37db9 + 3e7741b commit a630d65
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 340 deletions.
3 changes: 2 additions & 1 deletion packages/reactive_phone_form_field/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [2.0.2]
## [3.0.0]

* Support `reactive_forms: 17.x`
* Support `phone_form_field: 9.x` (read [BREAKING CHANGES](https://pub.dev/packages/phone_form_field/changelog#900))

## [2.0.1]

Expand Down
30 changes: 30 additions & 0 deletions packages/reactive_phone_form_field/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class MyApp extends StatelessWidget {
isoCode: IsoCode.UA,
nsn: '933456789',
),
validators: [
PhoneValidators.required,
PhoneValidators.valid,
],
),
});

Expand All @@ -23,10 +27,31 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: [
...PhoneFieldLocalization.delegates,
],
supportedLocales: const [
Locale('en', ''),
Locale('es', ''),
Locale('fr', ''),
Locale('ru', ''),
Locale('uz', ''),
Locale('uk', ''),
Locale('ar', ''),
],
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (context, child) {
return ReactiveFormConfig(
validationMessages: {
// either configure validation messages globally, or for each control
...PhoneValidationMessage.localizedValidationMessages(context),
},
child: child!,
);
},
home: Scaffold(
appBar: AppBar(),
body: SafeArea(
Expand All @@ -44,6 +69,11 @@ class MyApp extends StatelessWidget {
ReactivePhoneFormField<PhoneNumber>(
formControlName: 'input',
focusNode: FocusNode(),
// validationMessages: {
// ...PhoneValidationMessage.localizedValidationMessages(
// context,
// ),
// },
),
const SizedBox(height: 16),
ElevatedButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@ library reactive_phone_form_field;
export 'src/reactive_phone_form_field.dart';
export 'src/validators/validators.dart';
export 'src/validators/validation_message.dart';
export 'src/validators/validator/required_validator.dart';
export 'src/validators/validator/valid_fixed_line_validator.dart';
export 'src/validators/validator/valid_mobile_validator.dart';
export 'src/validators/validator/valid_validator.dart';
export 'src/validators/validator/_exports.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export 'package:phone_form_field/phone_form_field.dart';
///
/// A [ReactiveForm] ancestor is required.
///
class ReactivePhoneFormField<T> extends ReactiveFormField<T, PhoneNumber> {
class ReactivePhoneFormField<T>
extends ReactiveFocusableFormField<T, PhoneNumber> {
final PhoneController? _textController;

/// Creates a [ReactivePhoneFormField] that contains a [PhoneFormField].
///
/// Can optionally provide a [formControl] to bind this widget to a control.
Expand Down Expand Up @@ -85,45 +88,32 @@ class ReactivePhoneFormField<T> extends ReactiveFormField<T, PhoneNumber> {
/// For documentation about the various parameters, see the [PhoneFormField] class
/// and [PhoneFormField], the constructor.
ReactivePhoneFormField({
Key? key,
String? formControlName,
FormControl<T>? formControl,
Map<String, ValidationMessageFunction>? validationMessages,
ControlValueAccessor<T, PhoneNumber>? valueAccessor,
ShowErrorsFunction<T>? showErrors,

super.key,
super.formControlName,
super.formControl,
super.validationMessages,
super.valueAccessor,
super.showErrors,
super.focusNode,
////////////////////////////////////////////////////////////////////////////
bool shouldFormat = true,
bool enabled = true,
bool showFlagInInput = true,
CountrySelectorNavigator countrySelectorNavigator =
const CountrySelectorNavigator.searchDelegate(),
const CountrySelectorNavigator.page(),
Function(PhoneNumber?)? onSaved,
IsoCode defaultCountry = IsoCode.US,
PhoneNumber? initialValue,
double flagSize = 16,
ReactiveFormFieldCallback<T>? onChanged,
InputDecoration decoration = const InputDecoration(),
TextInputType keyboardType = TextInputType.phone,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
bool? showCursor,
bool obscureText = false,
String obscuringCharacter = '•',
bool autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
GestureTapCallback? onTap,
VoidCallback? onEditingComplete,
List<TextInputFormatter>? inputFormatters,
double cursorWidth = 2.0,
Expand All @@ -133,76 +123,62 @@ class ReactivePhoneFormField<T> extends ReactiveFormField<T, PhoneNumber> {
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
VoidCallback? onSubmitted,
FocusNode? focusNode,
Iterable<String>? autofillHints,
MouseCursor? mouseCursor,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
AppPrivateCommandCallback? onAppPrivateCommand,
String? restorationId,
ScrollController? scrollController,
TextSelectionControls? selectionControls,
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
TextStyle? countryCodeStyle,
CountryButtonStyle countryButtonStyle = const CountryButtonStyle(),
bool enableIMEPersonalizedLearning = true,
bool isCountrySelectionEnabled = true,
bool isCountryChipPersistent = false,
}) : super(
key: key,
formControl: formControl,
formControlName: formControlName,
valueAccessor: valueAccessor,
validationMessages: validationMessages,
showErrors: showErrors,
bool isCountryButtonPersistent = false,
Widget Function(BuildContext, EditableTextState)? contextMenuBuilder,
Function(PointerDownEvent)? onTapOutside,
PhoneController? controller,
}) : _textController = controller,
super(
builder: (field) {
final state = field as _ReactivePhoneFormFieldState<T>;
final effectiveDecoration = decoration
.applyDefaults(Theme.of(state.context).inputDecorationTheme);

state._setFocusNode(focusNode);

return PhoneFormField(
countryCodeStyle: countryCodeStyle,
countryButtonStyle: countryButtonStyle,
focusNode: state.focusNode,
controller: state._textController,
shouldFormat: shouldFormat,
onChanged: field.didChange,
autofillHints: autofillHints,
contextMenuBuilder: contextMenuBuilder,
autofocus: autofocus,
enabled: field.control.enabled,
showFlagInInput: showFlagInInput,
countrySelectorNavigator: countrySelectorNavigator,
onSaved: onSaved,
defaultCountry: defaultCountry,
decoration: effectiveDecoration.copyWith(
errorText: field.errorText,
enabled: field.control.enabled,
),
cursorColor: cursorColor,
autovalidateMode: AutovalidateMode.disabled,
flagSize: flagSize,
onChanged: (value) {
field.didChange(value);
onChanged?.call(field.control);
},
onEditingComplete: onEditingComplete,
restorationId: restorationId,
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
showCursor: showCursor,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ??
(obscureText
? SmartDashesType.disabled
: SmartDashesType.enabled),
smartQuotesType: smartQuotesType ??
(obscureText
? SmartQuotesType.disabled
: SmartQuotesType.enabled),
smartDashesType: smartDashesType,
smartQuotesType: smartQuotesType,
onTapOutside: onTapOutside,
enableSuggestions: enableSuggestions,
onSubmitted: onSubmitted != null ? (_) => onSubmitted() : null,
inputFormatters: inputFormatters,
Expand All @@ -222,7 +198,7 @@ class ReactivePhoneFormField<T> extends ReactiveFormField<T, PhoneNumber> {
selectionWidthStyle: selectionWidthStyle,
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
isCountrySelectionEnabled: isCountrySelectionEnabled,
isCountryChipPersistent: isCountryChipPersistent,
isCountryButtonPersistent: isCountryButtonPersistent,
);
},
);
Expand All @@ -233,68 +209,40 @@ class ReactivePhoneFormField<T> extends ReactiveFormField<T, PhoneNumber> {
}

class _ReactivePhoneFormFieldState<T>
extends ReactiveFormFieldState<T, PhoneNumber> {
extends ReactiveFocusableFormFieldState<T, PhoneNumber> {
late PhoneController _textController;
FocusNode? _focusNode;
late FocusController _focusController;

@override
FocusNode get focusNode => _focusNode ?? _focusController.focusNode;
static const defaultPhone = PhoneNumber(isoCode: IsoCode.US, nsn: '');

@override
void initState() {
super.initState();

_textController = PhoneController(value);
}

@override
void subscribeControl() {
_registerFocusController(FocusController());
super.subscribeControl();
_initializeTextController();
}

@override
void unsubscribeControl() {
_unregisterFocusController();
super.unsubscribeControl();
void _initializeTextController() {
final initialValue = value;
final currentWidget = widget as ReactivePhoneFormField<T>;
_textController = (currentWidget._textController != null)
? currentWidget._textController!
: PhoneController(initialValue: initialValue ?? defaultPhone);
}

@override
void onControlValueChanged(dynamic value) {
if (value == null) {
_textController.value = null;
} else if (value is PhoneNumber) {
_textController.value = PhoneNumber(
isoCode: value.isoCode,
nsn: value.nsn,
);
if (value is PhoneNumber?) {
_textController.value = value ?? defaultPhone;
}

super.onControlValueChanged(value);
}

void _registerFocusController(FocusController focusController) {
_focusController = focusController;
control.registerFocusController(focusController);
}

void _unregisterFocusController() {
control.unregisterFocusController(_focusController);
_focusController.dispose();
}

@override
void dispose() {
_textController.dispose();
super.dispose();
}

void _setFocusNode(FocusNode? focusNode) {
if (_focusNode != focusNode) {
_focusNode = focusNode;
_unregisterFocusController();
_registerFocusController(FocusController(focusNode: _focusNode));
final currentWidget = widget as ReactivePhoneFormField<T>;
if (currentWidget._textController == null) {
_textController.dispose();
}
super.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
import 'package:flutter/material.dart';
import 'package:reactive_phone_form_field/reactive_phone_form_field.dart';

class PhoneValidationMessage {
static const String valid = 'phone.valid';
static const String validMobile = 'phone.validMobile';
static const String required = 'phone.required';
static const String invalidPhoneNumber = 'phone.invalidPhoneNumber';
static const String invalidCountry = 'phone.invalidCountry';
static const String invalidMobilePhoneNumber =
'phone.invalidMobilePhoneNumber';
static const String invalidFixedLinePhoneNumber =
'phone.invalidFixedLinePhoneNumber';

static Map<String, String Function(Object)> localizedValidationMessages(
BuildContext context,
) {
final localizations = PhoneFieldLocalization.of(context);
return {
PhoneValidationMessage.required: (error) =>
localizations.requiredPhoneNumber,
PhoneValidationMessage.invalidCountry: (error) =>
localizations.invalidCountry,
PhoneValidationMessage.invalidFixedLinePhoneNumber: (error) =>
localizations.invalidFixedLinePhoneNumber,
PhoneValidationMessage.invalidMobilePhoneNumber: (error) =>
localizations.invalidMobilePhoneNumber,
PhoneValidationMessage.invalidPhoneNumber: (error) =>
localizations.invalidPhoneNumber,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'country_phone_validator.dart';
export 'required_validator.dart';
export 'valid_validator.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:phone_form_field/phone_form_field.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_phone_form_field/reactive_phone_form_field.dart';

class CountryPhoneValidator extends Validator<PhoneNumber> {
final Set<IsoCode> allowedCountries;

const CountryPhoneValidator({
required this.allowedCountries,
});

@override
Map<String, Object>? validate(AbstractControl<PhoneNumber> control) {
final value = control.value;

if (value == null || value.nsn.trim().isEmpty) return null;

if (!allowedCountries.contains(value.isoCode)) {
return {PhoneValidationMessage.invalidCountry: allowedCountries};
}

return null;
}
}
Loading

0 comments on commit a630d65

Please sign in to comment.