phone_field_plus 1.0.1
phone_field_plus: ^1.0.1 copied to clipboard
Offline-first Flutter phone input with country picker and auto-detection.
example/lib/main.dart
// example/lib/main.dart
//
// Demonstrates all major PhoneField features.
import 'package:flutter/material.dart';
import 'package:phone_field_plus/phone_field_plus.dart';
void main() => runApp(const PhoneFieldExampleApp());
class PhoneFieldExampleApp extends StatelessWidget {
const PhoneFieldExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PhoneField+ Demo',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
),
home: const ExampleScreen(),
);
}
}
class ExampleScreen extends StatefulWidget {
const ExampleScreen({super.key});
@override
State<ExampleScreen> createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
PhoneValue? _lastValue;
final _formKey = GlobalKey<FormState>();
// Shared controller for the programmatic example
late final PhoneController _controller;
@override
void initState() {
super.initState();
_controller = PhoneController(
initialCountry: Countries.tunisia,
autoDetectCountry: true,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PhoneField+ Demo')),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
// ── Basic ──────────────────────────────────────────────────────────
_SectionHeader('Basic — auto-detect + auto-validate'),
PhoneField(
initialCountry: Countries.france,
autoDetectCountry: true,
autoValidate: true,
onChanged: (v) => setState(() => _lastValue = v),
decoration: const InputDecoration(
labelText: 'Phone number',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
if (_lastValue != null) _ValueCard(_lastValue!),
const SizedBox(height: 32),
// ── Loose validation ───────────────────────────────────────────────
_SectionHeader('Loose validation mode'),
PhoneField(
initialCountry: Countries.unitedStates,
autoValidate: true,
validatorMode: ValidatorMode.loose,
decoration: const InputDecoration(
labelText: 'US / CA number (loose)',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 32),
// ── Custom flag builder ────────────────────────────────────────────
_SectionHeader('Custom flag builder (ISO badge)'),
PhoneField(
initialCountry: Countries.germany,
flagBuilder: (country) => Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.indigo.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
country.isoCode,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
),
),
decoration: const InputDecoration(
labelText: 'German number',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 32),
// ── Programmatic controller ────────────────────────────────────────
_SectionHeader('Programmatic controller (Tunisia default)'),
PhoneField(
controller: _controller,
autoValidate: true,
decoration: const InputDecoration(
labelText: 'Controlled field',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
Row(
children: [
OutlinedButton(
onPressed: () => _controller.setCountry(Countries.morocco),
child: const Text('Switch to Morocco'),
),
const SizedBox(width: 12),
OutlinedButton(
onPressed: () {
_controller.clear();
_controller.setCountry(Countries.tunisia);
},
child: const Text('Reset'),
),
],
),
const SizedBox(height: 32),
// ── Form integration ───────────────────────────────────────────────
_SectionHeader('Form integration with custom validator'),
Form(
key: _formKey,
child: Column(
children: [
PhoneField(
initialCountry: Countries.saudiArabia,
autoValidate: false,
validator: (v) {
if (!v.hasNumber) return 'Required';
if (!v.isValid) {
return 'Invalid ${v.country.name} number';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Saudi number',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () => _formKey.currentState?.validate(),
child: const Text('Validate'),
),
],
),
),
const SizedBox(height: 48),
],
),
);
}
}
class _SectionHeader extends StatelessWidget {
final String title;
const _SectionHeader(this.title);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Colors.grey.shade600,
fontWeight: FontWeight.w600,
),
),
);
}
}
class _ValueCard extends StatelessWidget {
final PhoneValue value;
const _ValueCard(this.value);
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Row('Country', '${value.country.flagEmoji} ${value.country.name}'),
_Row('E.164', value.e164),
_Row('National', value.nationalNumber),
_Row('Valid', value.isValid ? '✅ Yes' : '❌ No'),
],
),
),
);
}
}
class _Row extends StatelessWidget {
final String label;
final String value;
const _Row(this.label, this.value);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
SizedBox(
width: 80,
child: Text(
label,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 12,
color: Colors.grey,
),
),
),
Expanded(child: Text(value, style: const TextStyle(fontSize: 13))),
],
),
);
}
}