/// Vietnamese Phone Number Input Field /// /// Specialized input field for Vietnamese phone numbers with /// auto-formatting and validation. library; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/utils/formatters.dart'; import 'package:worker/core/utils/validators.dart'; /// Phone number input field with Vietnamese formatting class VietnamesePhoneField extends StatefulWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final String? initialValue; final ValueChanged? onChanged; final ValueChanged? onSubmitted; final FormFieldValidator? validator; final bool enabled; final bool autoFocus; final TextInputAction? textInputAction; final FocusNode? focusNode; final bool required; final Widget? prefixIcon; final Widget? suffixIcon; const VietnamesePhoneField({ super.key, this.controller, this.labelText, this.hintText, this.initialValue, this.onChanged, this.onSubmitted, this.validator, this.enabled = true, this.autoFocus = false, this.textInputAction, this.focusNode, this.required = true, this.prefixIcon, this.suffixIcon, }); @override State createState() => _VietnamesePhoneFieldState(); } class _VietnamesePhoneFieldState extends State { late TextEditingController _controller; bool _isControllerInternal = false; @override void initState() { super.initState(); if (widget.controller == null) { _controller = TextEditingController(text: widget.initialValue); _isControllerInternal = true; } else { _controller = widget.controller!; } } @override void dispose() { if (_isControllerInternal) { _controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return TextFormField( controller: _controller, focusNode: widget.focusNode, enabled: widget.enabled, autofocus: widget.autoFocus, keyboardType: TextInputType.phone, textInputAction: widget.textInputAction ?? TextInputAction.next, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(11), _PhoneNumberFormatter(), ], decoration: InputDecoration( labelText: widget.labelText ?? 'Số điện thoại', hintText: widget.hintText ?? '0xxx xxx xxx', prefixIcon: widget.prefixIcon ?? const Icon(Icons.phone), suffixIcon: widget.suffixIcon, border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), validator: widget.validator ?? (widget.required ? Validators.phone : Validators.phoneOptional), onChanged: widget.onChanged, onFieldSubmitted: widget.onSubmitted, ); } } /// Phone number text input formatter class _PhoneNumberFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { final text = newValue.text; if (text.isEmpty) { return newValue; } // Format as: 0xxx xxx xxx String formatted = text; if (text.length > 4 && text.length <= 7) { formatted = '${text.substring(0, 4)} ${text.substring(4)}'; } else if (text.length > 7) { formatted = '${text.substring(0, 4)} ${text.substring(4, 7)} ${text.substring(7)}'; } return TextEditingValue( text: formatted, selection: TextSelection.collapsed(offset: formatted.length), ); } } /// Read-only phone display field class PhoneDisplayField extends StatelessWidget { final String phoneNumber; final String? labelText; final Widget? prefixIcon; final VoidCallback? onTap; const PhoneDisplayField({ super.key, required this.phoneNumber, this.labelText, this.prefixIcon, this.onTap, }); @override Widget build(BuildContext context) { return TextFormField( initialValue: PhoneFormatter.format(phoneNumber), readOnly: true, enabled: onTap != null, onTap: onTap, decoration: InputDecoration( labelText: labelText ?? 'Số điện thoại', prefixIcon: prefixIcon ?? const Icon(Icons.phone), suffixIcon: onTap != null ? const Icon(Icons.edit) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), ); } } /// Phone field with country code selector class InternationalPhoneField extends StatefulWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final ValueChanged? onChanged; final FormFieldValidator? validator; final bool enabled; final String defaultCountryCode; const InternationalPhoneField({ super.key, this.controller, this.labelText, this.hintText, this.onChanged, this.validator, this.enabled = true, this.defaultCountryCode = '+84', }); @override State createState() => _InternationalPhoneFieldState(); } class _InternationalPhoneFieldState extends State { late TextEditingController _controller; late String _selectedCountryCode; bool _isControllerInternal = false; @override void initState() { super.initState(); _selectedCountryCode = widget.defaultCountryCode; if (widget.controller == null) { _controller = TextEditingController(); _isControllerInternal = true; } else { _controller = widget.controller!; } } @override void dispose() { if (_isControllerInternal) { _controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return TextFormField( controller: _controller, enabled: widget.enabled, keyboardType: TextInputType.phone, textInputAction: TextInputAction.next, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( labelText: widget.labelText ?? 'Số điện thoại', hintText: widget.hintText ?? 'xxx xxx xxx', prefixIcon: Container( padding: const EdgeInsets.symmetric(horizontal: 12), child: DropdownButton( value: _selectedCountryCode, underline: const SizedBox(), items: const [ DropdownMenuItem(value: '+84', child: Text('+84')), DropdownMenuItem(value: '+1', child: Text('+1')), DropdownMenuItem(value: '+86', child: Text('+86')), ], onChanged: widget.enabled ? (value) { if (value != null) { setState(() { _selectedCountryCode = value; }); widget.onChanged?.call('$value${_controller.text}'); } } : null, ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), validator: widget.validator, onChanged: (value) { widget.onChanged?.call('$_selectedCountryCode$value'); }, ); } }