Nuevos cambios hechos de diseño

This commit is contained in:
ellecio2
2023-08-23 17:33:44 -04:00
parent 7a806f84ff
commit d2e9ba53ab
3485 changed files with 691106 additions and 0 deletions

View File

@@ -0,0 +1,648 @@
import 'package:active_ecommerce_seller_app/custom/buttons.dart';
import 'package:active_ecommerce_seller_app/my_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl_phone_number_input/src/models/country_list.dart';
import 'package:intl_phone_number_input/src/models/country_model.dart';
import 'package:intl_phone_number_input/src/providers/country_provider.dart';
import 'package:intl_phone_number_input/src/utils/formatter/as_you_type_formatter.dart';
import 'package:intl_phone_number_input/src/utils/phone_number.dart';
import 'package:intl_phone_number_input/src/utils/phone_number/phone_number_util.dart';
import 'package:intl_phone_number_input/src/utils/selector_config.dart';
import 'package:intl_phone_number_input/src/utils/test/test_helper.dart';
import 'package:intl_phone_number_input/src/utils/util.dart';
import 'package:intl_phone_number_input/src/utils/widget_view.dart';
import 'package:intl_phone_number_input/src/utils/selector_config.dart';
import 'package:intl_phone_number_input/src/widgets/countries_search_list_widget.dart';
import 'package:intl_phone_number_input/src/widgets/input_widget.dart';
import 'package:intl_phone_number_input/src/widgets/item.dart';
/// Enum for [CustomSelectorButton] types.
///
/// Available type includes:
/// * [PhoneInputSelectorType.DROPDOWN]
/// * [PhoneInputSelectorType.BOTTOM_SHEET]
/// * [PhoneInputSelectorType.DIALOG]
/// A [TextFormField] for [CustomInternationalPhoneNumberInput].
///
/// [initialValue] accepts a [PhoneNumber] this is used to set initial values
/// for phone the input field and the selector button
///
/// [selectorButtonOnErrorPadding] is a double which is used to align the selector
/// button with the input field when an error occurs
///
/// [locale] accepts a country locale which will be used to translation, if the
/// translation exist
///
/// [countries] accepts list of string on Country isoCode, if specified filters
/// available countries to match the [countries] specified.
class CustomInternationalPhoneNumberInput extends StatefulWidget {
final SelectorConfig selectorConfig;
final ValueChanged<PhoneNumber> onInputChanged;
final ValueChanged<bool>? onInputValidated;
final VoidCallback? onSubmit;
final ValueChanged<String>? onFieldSubmitted;
final String Function(String?)? validator;
final ValueChanged<PhoneNumber>? onSaved;
final TextEditingController? textFieldController;
final TextInputType keyboardType;
final TextInputAction? keyboardAction;
final PhoneNumber? initialValue;
final String hintText;
final String errorMessage;
final double selectorButtonOnErrorPadding;
final double spaceBetweenSelectorAndTextField;
final int maxLength;
final bool isEnabled;
final bool formatInput;
final bool autoFocus;
final bool autoFocusSearch;
final AutovalidateMode autoValidateMode;
final bool ignoreBlank;
final bool countrySelectorScrollControlled;
final String? locale;
final TextStyle? textStyle;
final TextStyle? selectorTextStyle;
final InputBorder? inputBorder;
final InputDecoration? inputDecoration;
final InputDecoration? searchBoxDecoration;
final Color? cursorColor;
final TextAlign textAlign;
final TextAlignVertical textAlignVertical;
final EdgeInsets scrollPadding;
final FocusNode? focusNode;
final Iterable<String>? autofillHints;
final List<String?>? countries;
CustomInternationalPhoneNumberInput(
{Key? key,
this.selectorConfig = const SelectorConfig(),
required this.onInputChanged,
this.onInputValidated,
this.onSubmit,
this.onFieldSubmitted,
this.validator,
this.onSaved,
this.textFieldController,
this.keyboardAction,
this.keyboardType = TextInputType.phone,
this.initialValue,
this.hintText = 'Phone number',
this.errorMessage = 'Invalid phone number',
this.selectorButtonOnErrorPadding = 24,
this.spaceBetweenSelectorAndTextField = 12,
this.maxLength = 15,
this.isEnabled = true,
this.formatInput = true,
this.autoFocus = false,
this.autoFocusSearch = false,
this.autoValidateMode = AutovalidateMode.disabled,
this.ignoreBlank = false,
this.countrySelectorScrollControlled = true,
this.locale,
this.textStyle,
this.selectorTextStyle,
this.inputBorder,
this.inputDecoration,
this.searchBoxDecoration,
this.textAlign = TextAlign.start,
this.textAlignVertical = TextAlignVertical.center,
this.scrollPadding = const EdgeInsets.all(20.0),
this.focusNode,
this.cursorColor,
this.autofillHints,
this.countries})
: super(key: key);
@override
State<StatefulWidget> createState() => _InputWidgetState();
}
class _InputWidgetState extends State<CustomInternationalPhoneNumberInput> {
TextEditingController? controller;
double selectorButtonBottomPadding = 0;
Country? country;
List<Country> countries = [];
bool isNotValid = true;
@override
void initState() {
super.initState();
loadCountries();
controller = widget.textFieldController ?? TextEditingController();
initialiseWidget();
}
@override
void setState(fn) {
if (this.mounted) {
super.setState(fn);
}
}
@override
Widget build(BuildContext context) {
return _InputWidgetView(
state: this,
);
}
@override
void didUpdateWidget(CustomInternationalPhoneNumberInput oldWidget) {
if (oldWidget?.initialValue?.hash != widget?.initialValue?.hash) {
loadCountries();
initialiseWidget();
} else {
loadCountries(previouslySelectedCountry: country);
}
super.didUpdateWidget(oldWidget);
}
/// [initialiseWidget] sets initial values of the widget
void initialiseWidget() async {
if (widget.initialValue != null) {
if (widget.initialValue!.phoneNumber != null &&
widget.initialValue!.phoneNumber!.isNotEmpty &&
(await PhoneNumberUtil.isValidNumber(
phoneNumber: widget.initialValue!.phoneNumber!,
isoCode: widget.initialValue!.isoCode!))!) {
controller!.text =
await PhoneNumber.getParsableNumber(widget.initialValue!);
phoneNumberControllerListener();
}
}
}
/// loads countries from [Countries.countryList] and selected Country
void loadCountries({Country? previouslySelectedCountry}) {
if (this.mounted) {
List<Country> countries =
CountryProvider.getCountriesData(countries: widget.countries!.cast<String>());
final CountryComparator? countryComparator =
widget.selectorConfig?.countryComparator;
if (countryComparator != null) {
countries.sort(countryComparator);
}
Country country = previouslySelectedCountry ??
Utils.getInitialSelectedCountry(
countries,
widget.initialValue?.isoCode ?? '',
);
setState(() {
this.countries = countries;
this.country = country;
});
}
}
/// Listener that validates changes from the widget, returns a bool to
/// the `ValueCallback` [widget.onInputValidated]
void phoneNumberControllerListener() {
if (this.mounted) {
String parsedPhoneNumberString =
controller!.text.replaceAll(RegExp(r'[^\d+]'), '');
getParsedPhoneNumber(parsedPhoneNumberString, this.country?.alpha2Code)
.then((phoneNumber) {
if (phoneNumber == null) {
String phoneNumber =
'${this.country?.dialCode}$parsedPhoneNumberString';
if (widget.onInputChanged != null) {
widget.onInputChanged(PhoneNumber(
phoneNumber: phoneNumber,
isoCode: this.country?.alpha2Code,
dialCode: this.country?.dialCode));
}
if (widget.onInputValidated != null) {
widget.onInputValidated!(false);
}
this.isNotValid = true;
} else {
if (widget.onInputChanged != null) {
widget.onInputChanged(PhoneNumber(
phoneNumber: phoneNumber,
isoCode: this.country?.alpha2Code,
dialCode: this.country?.dialCode));
}
if (widget.onInputValidated != null) {
widget.onInputValidated!(true);
}
this.isNotValid = false;
}
});
}
}
/// Returns a formatted String of [phoneNumber] with [isoCode], returns `null`
/// if [phoneNumber] is not valid or if an [Exception] is caught.
Future<String?> getParsedPhoneNumber(
String phoneNumber, String? isoCode) async {
if (phoneNumber.isNotEmpty && isoCode != null) {
try {
bool isValidPhoneNumber = (await PhoneNumberUtil.isValidNumber(
phoneNumber: phoneNumber, isoCode: isoCode))!;
if (isValidPhoneNumber) {
return await PhoneNumberUtil.normalizePhoneNumber(
phoneNumber: phoneNumber, isoCode: isoCode);
}
} on Exception {
return null;
}
}
return null;
}
/// Creates or Select [InputDecoration]
InputDecoration getInputDecoration(InputDecoration? decoration) {
InputDecoration value = decoration ??
InputDecoration(
border: widget.inputBorder ?? UnderlineInputBorder(),
hintText: widget.hintText,
);
if (widget.selectorConfig.setSelectorButtonAsPrefixIcon) {
return value.copyWith(
prefixIcon: CustomSelectorButton(
country: country,
countries: countries,
onCountryChanged: onCountryChanged,
selectorConfig: widget.selectorConfig,
selectorTextStyle: widget.selectorTextStyle,
searchBoxDecoration: widget.searchBoxDecoration,
locale: locale,
isEnabled: widget.isEnabled,
autoFocusSearchField: widget.autoFocusSearch,
isScrollControlled: widget.countrySelectorScrollControlled,
));
}
return value;
}
/// Validate the phone number when a change occurs
void onChanged(String value) {
phoneNumberControllerListener();
}
/// Validate and returns a validation error when [FormState] validate is called.
///
/// Also updates [selectorButtonBottomPadding]
String? validator(String? value) {
bool isValid =
this.isNotValid && (value!.isNotEmpty || widget.ignoreBlank == false);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (isValid && widget.errorMessage != null) {
setState(() {
this.selectorButtonBottomPadding =
widget.selectorButtonOnErrorPadding ?? 24;
});
} else {
setState(() {
this.selectorButtonBottomPadding = 0;
});
}
});
return isValid ? widget.errorMessage : null;
}
/// Changes Selector Button Country and Validate Change.
void onCountryChanged(Country? country) {
setState(() {
this.country = country;
});
phoneNumberControllerListener();
}
void _phoneNumberSaved() {
if (this.mounted) {
String parsedPhoneNumberString =
controller!.text.replaceAll(RegExp(r'[^\d+]'), '');
getParsedPhoneNumber(parsedPhoneNumberString, this.country?.alpha2Code)
.then(
(phoneNumber) => widget.onSaved?.call(
PhoneNumber(
phoneNumber: phoneNumber,
isoCode: this.country?.alpha2Code,
dialCode: this.country?.dialCode),
),
);
}
}
/// Saved the phone number when form is saved
void onSaved(String? value) {
_phoneNumberSaved();
}
/// Corrects duplicate locale
String? get locale {
if (widget.locale == null) return null;
if (widget.locale!.toLowerCase() == 'nb' ||
widget.locale!.toLowerCase() == 'nn') {
return 'no';
}
return widget.locale;
}
}
class _InputWidgetView
extends WidgetView<CustomInternationalPhoneNumberInput, _InputWidgetState> {
final _InputWidgetState state;
_InputWidgetView({Key? key, required this.state})
: super(key: key, state: state);
@override
Widget build(BuildContext context) {
final countryCode = state?.country?.alpha2Code ?? '';
final dialCode = state?.country?.dialCode ?? '';
return Container(
child: Row(
textDirection: TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CustomSelectorButton(
country: state.country,
countries: state.countries,
onCountryChanged: state.onCountryChanged,
selectorConfig: widget.selectorConfig,
selectorTextStyle: widget.selectorTextStyle,
searchBoxDecoration: widget.searchBoxDecoration,
locale: state.locale,
isEnabled: widget.isEnabled,
autoFocusSearchField: widget.autoFocusSearch,
isScrollControlled: widget.countrySelectorScrollControlled,
),
SizedBox(
height: state.selectorButtonBottomPadding,
),
],
),
Flexible(
child: TextFormField(
key: Key(TestHelper.TextInputKeyValue),
textDirection: TextDirection.ltr,
controller: state.controller,
focusNode: widget.focusNode,
enabled: widget.isEnabled,
autofocus: widget.autoFocus,
keyboardType: widget.keyboardType,
textInputAction: widget.keyboardAction,
style: widget.textStyle,
decoration: state.getInputDecoration(widget.inputDecoration),
textAlign: widget.textAlign,
textAlignVertical: widget.textAlignVertical,
onEditingComplete: widget.onSubmit,
onFieldSubmitted: widget.onFieldSubmitted,
autovalidateMode: widget.autoValidateMode,
autofillHints: widget.autofillHints,
validator: widget.validator ?? state.validator,
onSaved: state.onSaved,
scrollPadding: widget.scrollPadding,
inputFormatters: [
LengthLimitingTextInputFormatter(widget.maxLength),
widget.formatInput
? AsYouTypeFormatter(
isoCode: countryCode,
dialCode: dialCode,
onInputFormatted: (TextEditingValue value) {
state.controller!.value = value;
},
)
: FilteringTextInputFormatter.digitsOnly,
],
onChanged: state.onChanged,
),
)
],
),
);
}
}
/// [CustomSelectorButton]
class CustomSelectorButton extends StatelessWidget {
final List<Country> countries;
final Country? country;
final SelectorConfig selectorConfig;
final TextStyle? selectorTextStyle;
final InputDecoration? searchBoxDecoration;
final bool autoFocusSearchField;
final String? locale;
final bool isEnabled;
final bool isScrollControlled;
final ValueChanged<Country?> onCountryChanged;
const CustomSelectorButton({
Key? key,
required this.countries,
required this.country,
required this.selectorConfig,
required this.selectorTextStyle,
required this.searchBoxDecoration,
required this.autoFocusSearchField,
required this.locale,
required this.onCountryChanged,
required this.isEnabled,
required this.isScrollControlled,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return selectorConfig.selectorType == PhoneInputSelectorType.DROPDOWN
? countries.isNotEmpty && countries.length > 1
? DropdownButtonHideUnderline(
child: DropdownButton<Country>(
key: Key(TestHelper.DropdownButtonKeyValue),
hint: Item(
country: country,
showFlag: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
textStyle: selectorTextStyle,
),
value: country,
items: mapCountryToDropdownItem(countries),
onChanged: isEnabled ? onCountryChanged : null,
),
)
: Item(
country: country,
showFlag: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
textStyle: selectorTextStyle,
)
: Container(
height: 36,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: MyTheme.textfield_grey,
width: .5
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(5.0),
bottomLeft: Radius.circular(5.0))),
child: Buttons(
key: Key(TestHelper.DropdownButtonKeyValue),
padding: EdgeInsets.zero,
color: MyTheme.grey_153,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topLeft: const Radius.circular(5.0),
bottomLeft: const Radius.circular(5.0),
)),
onPressed: countries.isNotEmpty &&
countries.length > 1 &&
isEnabled
? () async {
Country? selected;
if (selectorConfig.selectorType ==
PhoneInputSelectorType.BOTTOM_SHEET) {
selected = await showCountrySelectorBottomSheet(
context, countries);
} else {
selected =
await showCountrySelectorDialog(context, countries);
}
if (selected != null) {
onCountryChanged(selected);
}
}
: null,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Item(
country: country,
showFlag: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
textStyle: TextStyle(
color: MyTheme.textfield_grey) //selectorTextStyle,
),
),
),
);
}
/// Converts the list [countries] to `DropdownMenuItem`
List<DropdownMenuItem<Country>> mapCountryToDropdownItem(
List<Country> countries) {
return countries.map((country) {
return DropdownMenuItem<Country>(
value: country,
child: Item(
key: Key(TestHelper.countryItemKeyValue(country.alpha2Code)),
country: country,
showFlag: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
textStyle: selectorTextStyle,
withCountryNames: false,
),
);
}).toList();
}
/// shows a Dialog with list [countries] if the [PhoneInputSelectorType.DIALOG] is selected
Future<Country?> showCountrySelectorDialog(
BuildContext context, List<Country> countries) {
return showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
content: Container(
width: double.maxFinite,
child: CountrySearchListWidget(
countries,
locale,
searchBoxDecoration: searchBoxDecoration,
showFlags: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
autoFocus: autoFocusSearchField,
),
),
),
);
}
/// shows a Dialog with list [countries] if the [PhoneInputSelectorType.BOTTOM_SHEET] is selected
Future<Country?> showCountrySelectorBottomSheet(
BuildContext context, List<Country> countries) {
return showModalBottomSheet(
context: context,
clipBehavior: Clip.hardEdge,
isScrollControlled: isScrollControlled ?? true,
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12), topRight: Radius.circular(12))),
builder: (BuildContext context) {
return AnimatedPadding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
duration: const Duration(milliseconds: 100),
child: Stack(children: [
GestureDetector(
onTap: () => Navigator.pop(context),
),
DraggableScrollableSheet(
builder: (BuildContext context, ScrollController controller) {
return Container(
decoration: ShapeDecoration(
color: Theme.of(context).canvasColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
),
child: CountrySearchListWidget(
countries,
locale,
searchBoxDecoration: searchBoxDecoration,
scrollController: controller,
showFlags: selectorConfig.showFlags,
useEmoji: selectorConfig.useEmoji,
autoFocus: autoFocusSearchField,
),
);
},
),
]),
);
},
);
}
}