Replace intl_phone_field with intl_country_data for accurate phone validation#6063
Conversation
…lidation The intl_phone_field package had incorrect min/max phone number lengths for several countries (e.g. Finland required exactly 12 digits instead of 7-14), causing valid phone numbers to be rejected. Switched to intl_country_data which only provides country data without unused UI components.
Greptile SummaryThis PR replaces the Confidence Score: 5/5Safe to merge — the migration is API-correct, fixes a real validation bug, and prior review feedback has been substantially addressed. All field names used against the new package match its documented API. The two previously flagged IntlCountryData.all() hot-path calls have been fixed. The only remaining issue is a single redundant all() call during widget construction, which is a minor style point with no runtime impact. No regressions introduced. app/lib/pages/phone_calls/phone_setup_number_page.dart — minor redundant IntlCountryData.all() at _filtered field init. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User taps country selector] --> B[_showCountryPicker]
B --> C[_CountryPickerSheet opens]
C --> D["_allCountries = IntlCountryData.all() [once at init]"]
D --> E["_filtered = IntlCountryData.all() [redundant init — see comment]"]
E --> F[Render country list]
F --> G{User types in search}
G -->|keystroke| H["_filter(query) — uses _allCountries cache"]
H --> I[setState _filtered = filtered subset]
I --> F
G -->|selects country| J[_selectedCountry updated]
J --> K[User enters phone digits]
K --> L{_isValid check}
L -->|"digits >= telephoneMinLength && digits <= telephoneMaxLength"| M[Enable Continue button]
L -->|invalid| N[Button disabled]
M --> O["_fullNumber = +telephoneCode + digits"]
O --> P[startVerification called]
Reviews (2): Last reviewed commit: "Cache country list in CountryPickerSheet..." | Re-trigger Greptile |
| for (var len = 3; len >= 1; len--) { | ||
| if (digits.length <= len) continue; | ||
| var candidate = digits.substring(0, len); | ||
| if (countries.any((c) => c.fullCountryCode == candidate)) { | ||
| if (IntlCountryData.all().any((c) => c.telephoneCode == candidate)) { | ||
| return '+$candidate'; | ||
| } | ||
| } |
There was a problem hiding this comment.
IntlCountryData.all() called on every loop iteration
IntlCountryData.all() is invoked up to 3 times inside the loop (once per iteration when len is 3, 2, and 1). If all() allocates a new list each call, this creates unnecessary allocations on every invocation of _extractCountryCode. Cache the result before the loop:
| for (var len = 3; len >= 1; len--) { | |
| if (digits.length <= len) continue; | |
| var candidate = digits.substring(0, len); | |
| if (countries.any((c) => c.fullCountryCode == candidate)) { | |
| if (IntlCountryData.all().any((c) => c.telephoneCode == candidate)) { | |
| return '+$candidate'; | |
| } | |
| } | |
| String? _extractCountryCode(String e164Number) { | |
| if (!e164Number.startsWith('+')) return null; | |
| var digits = e164Number.substring(1); // strip '+' | |
| final allCountries = IntlCountryData.all(); | |
| // Try longest match first (country codes are 1-3 digits) | |
| for (var len = 3; len >= 1; len--) { | |
| if (digits.length <= len) continue; | |
| var candidate = digits.substring(0, len); | |
| if (allCountries.any((c) => c.telephoneCode == candidate)) { | |
| return '+$candidate'; | |
| } | |
| } | |
| return null; | |
| } |
| _filtered = IntlCountryData.all().where((c) { | ||
| return c.name.toLowerCase().contains(q) || | ||
| c.telephoneCode.contains(q) || | ||
| c.codeAlpha2.toLowerCase().contains(q); | ||
| }).toList(); |
There was a problem hiding this comment.
IntlCountryData.all() called on every search keystroke
IntlCountryData.all() is re-invoked on every call to _filter, which fires on each keystroke. Consider caching the full country list as a field and filtering over the cached list to avoid repeated allocations:
| _filtered = IntlCountryData.all().where((c) { | |
| return c.name.toLowerCase().contains(q) || | |
| c.telephoneCode.contains(q) || | |
| c.codeAlpha2.toLowerCase().contains(q); | |
| }).toList(); | |
| _filtered = _allCountries.where((c) { | |
| return c.name.toLowerCase().contains(q) || | |
| c.telephoneCode.contains(q) || | |
| c.codeAlpha2.toLowerCase().contains(q); | |
| }).toList(); |
And add final List<IntlCountryData> _allCountries = IntlCountryData.all(); as a field on _CountryPickerSheetState.
These were accidentally included from pre-existing staged changes.
|
@greptile-apps re-review |
…lidation (BasedHardware#6063) ## Summary - Replaced `intl_phone_field` package with `intl_country_data` — we only used the country data, not the UI widget - Fixes phone number validation rejecting valid numbers for countries with incorrect min/max lengths in the old package (e.g. Finland required exactly 12 digits instead of the correct 7-14) - Updated all references in `phone_setup_number_page.dart` and `phone_calls_page.dart` to the new API ## Test plan - [ ] Select Finland (+358) in the country picker and enter a valid mobile number (e.g. `40 1234567` — 9 digits) — should be accepted - [ ] Verify US numbers still work as before - [ ] Open the country picker, search by name/code/dial code — all should filter correctly - [ ] Complete full phone verification flow end-to-end 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary
intl_phone_fieldpackage withintl_country_data— we only used the country data, not the UI widgetphone_setup_number_page.dartandphone_calls_page.dartto the new APITest plan
40 1234567— 9 digits) — should be accepted🤖 Generated with Claude Code