Skip to content

Commit 1de2658

Browse files
author
Ahmed Amine Zribi
committed
feat: resolve issue about exception
1 parent 35949e2 commit 1de2658

File tree

2 files changed

+115
-70
lines changed

2 files changed

+115
-70
lines changed

app/lib/ui/components/form_input/phone_number/phone_number_input_demo_screen.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ class _PhoneNumberInputDemoState extends State<_PhoneNumberInputDemo> {
131131
void onCountryChanged(Country country) {
132132
setState(() {
133133
selectedCountry = country;
134+
onFlagChanged();
135+
});
136+
}
137+
138+
void onFlagChanged() {
139+
setState(() {
140+
controller.text = '';
134141
});
135142
}
136143

ouds_core/lib/components/form_input/ouds_phone_number_input.dart

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
142142
final phoneUtil = PhoneNumberUtil.instance;
143143
int? maxLength;
144144

145+
/// Digits tapés par l'utilisateur
145146
@override
146147
void initState() {
147148
super.initState();
@@ -266,19 +267,19 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
266267
/// Center block: main text input
267268
Expanded(
268269
child: TextField(
269-
inputFormatters: [
270-
FilteringTextInputFormatter.digitsOnly,
271-
if (maxLength != null) LengthLimitingTextInputFormatter(maxLength),
272-
],
270+
controller: widget.controller,
273271
cursorColor: inputTextTextModifier.getCursorTextColor(state, isError),
274272
focusNode: effectiveFocusNode,
275-
controller: widget.controller,
276273
keyboardType: widget.keyboardType,
277274
style: theme.typographyTokens.typeLabelDefaultLarge(context).copyWith(
278275
color: inputTextTextModifier.getTextColor(state, isError),
279276
),
280277
enabled: widget.enabled,
281278
readOnly: widget.readOnly ?? false,
279+
inputFormatters: [
280+
FilteringTextInputFormatter.digitsOnly,
281+
MaxDigitsFormatter(getMaxDigitsFromLib(countrySelected.code)), // block extra digits
282+
],
282283
onTap: () {
283284
// send text tapped to parent
284285
widget.onEditingComplete?.call(widget.controller?.text ?? '');
@@ -296,7 +297,7 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
296297
widget.onEditingComplete?.call(value);
297298
},
298299
onChanged: (value) {
299-
_onCountryChanged(value, limitedDigits, formattedNumber);
300+
_onCountryChanged(value);
300301
},
301302
decoration: InputDecoration(
302303
border: InputBorder.none,
@@ -356,73 +357,74 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
356357

357358
/// Function `_onCountryChanged`
358359
///
359-
/// This function is triggered when the selected country or phone number changes.
360-
/// It updates the phone number formatting based on the selected country,
361-
/// limits the number length according to the country, and updates the text controller.
362-
///
363-
/// Parameters :
364-
/// - `value` : The new input value of the phone number entered by the user.
365-
/// - `limitedDigits` : The string containing only digits of the number, limited to the maximum length.
366-
/// - `formattedNumber` : The formatted version of the phone number, which will be updated in the controller.
367-
///
368-
/// Behavior :
369-
/// - Determines the selected country based on the prefix or current selection.
370-
/// - Cleans the number to keep only digits.
371-
/// - Limits the number length based on the country configuration.
372-
/// - Formats the number in national or international format depending on the configuration.
373-
/// - Updates the text controller with the formatted number, maintaining the cursor position.
374-
/// - If an error occurs during parsing or formatting, retains the raw digit version.
375-
void _onCountryChanged(String value, String limitedDigits, String? formattedNumber) {
376-
// Select a Country if prefix of decoration is add
377-
// if we take the prefix from current local or country selected
378-
if (widget.decoration.prefix != null) {
379-
countrySelected = CountryService().findCountryByPrefix(widget.decoration.prefix!) ?? Country.empty();
380-
} else {
381-
countrySelected = widget.countrySelector?.selectedCountry ?? Country.empty();
382-
}
383-
// Clean the input to keep only digits
384-
final digitsOnly = value.replaceAll(RegExp(r'\D'), '');
385-
386-
// Retrieve the maximum allowed length for the selected country
387-
maxLength = getMaxDigitsFromLib(countrySelected.code);
388-
// Store the maximum length for future reference
389-
//maxLength = maxDigits;
390-
391-
// Limit the input to the maximum length
392-
limitedDigits = digitsOnly;
393-
if (maxLength != null && digitsOnly.length > maxLength!) {
394-
limitedDigits = digitsOnly.substring(0, maxLength);
395-
}
360+
/// This function is triggered when the user changes the country selection or updates the phone number input.
361+
/// It formats the phone number based on the selected country, enforces a maximum digit length,
362+
/// and updates the text controller with the formatted number while maintaining cursor position.
363+
///
364+
/// Parameters:
365+
/// - `value`: The current input value of the phone number entered by the user.
366+
///
367+
/// Behavior:
368+
/// - Checks if the formatter is already processing to prevent re-entrancy.
369+
/// - Retrieves the selected country or defaults to an empty country.
370+
/// - Strips all non-digit characters from the input.
371+
/// - Retrieves the maximum allowed digits for the selected country.
372+
/// - Limits the number of digits to the maximum allowed.
373+
/// - Parses and validates the number using a phone utility library.
374+
/// - Formats the number in national format if valid.
375+
/// - Updates the text controller with the formatted number, preserving cursor position.
376+
/// - Handles parsing errors gracefully and retains the raw digit string if formatting fails.
377+
bool _isFormatting = false;
378+
379+
void _onCountryChanged(String value) {
380+
if (_isFormatting) return;
381+
_isFormatting = true;
396382

397-
// Format the number
398383
try {
399-
if (widget.countrySelector == null) {
400-
// Parse and format as national number if country selector is disabled
401-
final parsedNumber = phoneUtil.parse(limitedDigits, countrySelected.code.toUpperCase());
402-
formattedNumber = phoneUtil.format(parsedNumber, PhoneNumberFormat.national);
403-
} else {
404-
// Parse and format as international number if country selector is enabled
405-
final parsedNumber = phoneUtil.parse(limitedDigits, countrySelected.code.toUpperCase());
406-
String phoneNumber = phoneUtil.format(parsedNumber, PhoneNumberFormat.international);
407-
// Convert international number to national format
408-
formattedNumber = getNationalNumber(phoneNumber, countrySelected.code.toUpperCase());
384+
countrySelected = widget.countrySelector?.selectedCountry ?? Country.empty();
385+
386+
// Remove non-digit characters
387+
String digitsOnly = value.replaceAll(RegExp(r'\D'), '');
388+
389+
// Get max digits for country
390+
int? maxLength = getMaxDigitsFromLib(countrySelected.code);
391+
debugPrint("🌍 Country selected: ${countrySelected.code}");
392+
debugPrint("🔢 Digits only: $digitsOnly (raw input: $value)");
393+
debugPrint("📏 Max length from lib: $maxLength");
394+
395+
// Limit digits if longer than max
396+
if (digitsOnly.length > maxLength) {
397+
digitsOnly = digitsOnly.substring(0, maxLength);
398+
debugPrint("✂️ Cutting digits from ${digitsOnly.length} to $maxLength");
409399
}
410400

411-
// Update the controller's value with the formatted number
412-
final selectionIndex = widget.controller?.selection.baseOffset ?? 0;
401+
String formattedNumber = digitsOnly;
402+
bool isValidNumber = false;
413403

414-
widget.controller?.value = TextEditingValue(
415-
text: formattedNumber!,
416-
// Keep the cursor position after the inserted formatted number
417-
selection: TextSelection.collapsed(offset: selectionIndex + (formattedNumber.length)),
418-
);
419-
} catch (e) {
420-
// If an error occurs during parsing or formatting, keep the raw digits
421-
widget.controller?.value = TextEditingValue(
422-
text: limitedDigits,
423-
// Place cursor at the end of the input
424-
selection: TextSelection.collapsed(offset: limitedDigits.length),
425-
);
404+
try {
405+
final parsed = phoneUtil.parse(digitsOnly, countrySelected.code.toUpperCase());
406+
isValidNumber = phoneUtil.isValidNumber(parsed);
407+
408+
if (isValidNumber) {
409+
formattedNumber = phoneUtil.format(parsed, PhoneNumberFormat.national);
410+
debugPrint("🎯 Formatted national: $formattedNumber");
411+
}
412+
} catch (e) {
413+
debugPrint("🚨 Parsing error: $e");
414+
}
415+
416+
debugPrint("✅ Is valid: $isValidNumber");
417+
418+
// Update controller only if text actually changes
419+
if (widget.controller?.text != formattedNumber) {
420+
debugPrint("✏️ Final text update: $formattedNumber");
421+
widget.controller?.value = TextEditingValue(
422+
text: formattedNumber,
423+
selection: TextSelection.collapsed(offset: formattedNumber.length),
424+
);
425+
}
426+
} finally {
427+
_isFormatting = false;
426428
}
427429
}
428430

@@ -639,15 +641,15 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
639641
/// Returns:
640642
/// - The maximum number of digits as an integer if successful.
641643
/// - Null if the example number cannot be retrieved or an error occurs.
642-
int? getMaxDigitsFromLib(String countryCode) {
644+
int getMaxDigitsFromLib(String countryCode) {
643645
try {
644646
final exampleNumber = phoneUtil.getExampleNumber(countryCode.toUpperCase());
645647
if (exampleNumber != null && exampleNumber.nationalNumber.toString().isNotEmpty) {
646648
return exampleNumber.nationalNumber.toString().length;
647649
}
648650
//}
649651
} catch (_) {}
650-
return null;
652+
return 0;
651653
}
652654

653655
/// Converts an international phone number to its national format for a specific country.
@@ -675,3 +677,39 @@ class _OudsPhoneNumberInputState extends State<OudsPhoneNumberInput> {
675677
}
676678
}
677679
}
680+
681+
/// A custom [TextInputFormatter] that limits the number of digits a user can input.
682+
///
683+
/// This formatter allows only numeric input and enforces a maximum number of digits.
684+
/// It strips out all non-digit characters and prevents further input once the limit is reached.
685+
///
686+
/// Parameters:
687+
/// - [maxDigits]: The maximum number of digits allowed in the input.
688+
///
689+
/// Usage:
690+
/// ```dart
691+
/// TextField(
692+
/// keyboardType: TextInputType.number,
693+
/// inputFormatters: [MaxDigitsFormatter(10)],
694+
/// )
695+
/// ```
696+
/// This example restricts the input to a maximum of 10 digits.
697+
class MaxDigitsFormatter extends TextInputFormatter {
698+
final int maxDigits;
699+
700+
/// Creates a [MaxDigitsFormatter] with the specified maximum number of digits.
701+
MaxDigitsFormatter(this.maxDigits);
702+
703+
@override
704+
TextEditingValue formatEditUpdate(
705+
TextEditingValue oldValue,
706+
TextEditingValue newValue,
707+
) {
708+
final digits = newValue.text.replaceAll(RegExp(r'\D'), '');
709+
if (digits.length > maxDigits) {
710+
debugPrint("🛑 Blocked at $maxDigits digits");
711+
return oldValue; // Stop typing
712+
}
713+
return newValue;
714+
}
715+
}

0 commit comments

Comments
 (0)