country_kit 1.0.1 copy "country_kit: ^1.0.1" to clipboard
country_kit: ^1.0.1 copied to clipboard

Country data + flag widgets for building phone pickers. ISO codes, dial codes, currencies, localized names, phone masks, and SVG flags in circle, square, and rectangle shapes.

example/lib/main.dart

import 'package:country_kit/country_kit.dart';
import 'package:country_kit/l10n/fr.dart';
import 'package:flutter/material.dart';

void main() {
  // Translations are opt-in: only imported languages ship in the binary.
  registerCountryNamesFr();
  runApp(const ExampleApp());
}

/// [Continent] ships no display name on purpose — the enum stays free of
/// presentation, and apps map it to whatever their UI needs (localized
/// strings, icons, short codes). This is that mapping for the demo.
extension on Continent {
  String get label => switch (this) {
        Continent.africa => 'Africa',
        Continent.americas => 'Americas',
        Continent.antarctic => 'Antarctic',
        Continent.asia => 'Asia',
        Continent.europe => 'Europe',
        Continent.oceania => 'Oceania',
      };
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'country_kit demo',
      theme: ThemeData(colorSchemeSeed: Colors.teal),
      home: const DemoHome(),
    );
  }
}

class DemoHome extends StatelessWidget {
  const DemoHome({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('country_kit'),
          bottom: const TabBar(tabs: [
            Tab(text: 'Country selector'),
            Tab(text: 'Phone picker'),
            Tab(text: 'Special flags'),
          ]),
        ),
        body: const TabBarView(
          children: [
            CountrySelectorDemo(),
            PhonePickerDemo(),
            SpecialFlagsDemo(),
          ],
        ),
      ),
    );
  }
}

/// Searchable country list built from [Countries.search] + [CountryFlag].
class CountrySelectorDemo extends StatefulWidget {
  const CountrySelectorDemo({super.key});

  @override
  State<CountrySelectorDemo> createState() => _CountrySelectorDemoState();
}

class _CountrySelectorDemoState extends State<CountrySelectorDemo> {
  final _search = TextEditingController();
  String _query = '';
  Continent? _continent;

  @override
  void dispose() {
    _search.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Search ignores the continent filter: a query is a stronger signal of
    // intent than a chip the user set earlier.
    final countries = _query.isNotEmpty
        ? Countries.search(_query)
        : _continent == null
            ? Countries.all
            : Countries.byContinent(_continent!);
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(12),
          child: TextField(
            controller: _search,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.search),
              hintText: 'Search name, code or dial code',
              border: OutlineInputBorder(),
            ),
            onChanged: (q) => setState(() => _query = q),
          ),
        ),
        SizedBox(
          height: 40,
          child: ListView(
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 12),
            children: [
              for (final continent in Continent.values)
                Padding(
                  padding: const EdgeInsets.only(right: 8),
                  child: FilterChip(
                    label: Text(continent.label),
                    selected: _continent == continent,
                    onSelected: (on) => setState(() {
                      _continent = on ? continent : null;
                      _query = '';
                      _search.clear();
                    }),
                  ),
                ),
            ],
          ),
        ),
        Expanded(
          child: ListView.builder(
            itemCount: countries.length,
            itemBuilder: (context, i) {
              final c = countries[i];
              return ListTile(
                leading: CountryFlag(
                  country: c,
                  shape: FlagShape.circle,
                  size: 36,
                  borderRadius: BorderRadius.circular(1000),
                ),
                title: Text(c.name),
                subtitle: Text(
                  '${c.nativeName ?? c.officialName} · ${c.continent.label}',
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
                trailing: Text(c.dialCode ?? ''),
                onTap: () => ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text(
                      '${c.flagEmoji} ${c.name} — ${c.alpha3}, '
                      '${c.currencyCode ?? 'no currency'}',
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

/// Phone input with a dial-code picker prefix, using rectangle flags and the
/// per-country phone mask/length metadata.
class PhonePickerDemo extends StatefulWidget {
  const PhonePickerDemo({super.key});

  @override
  State<PhonePickerDemo> createState() => _PhonePickerDemoState();
}

class _PhonePickerDemoState extends State<PhonePickerDemo> {
  late Country _country = Countries.current ?? Countries.byAlpha2('US')!;
  final _phone = TextEditingController();

  @override
  void dispose() {
    _phone.dispose();
    super.dispose();
  }

  Future<void> _pickCountry() async {
    final picked = await showModalBottomSheet<Country>(
      context: context,
      builder: (context) => ListView(
        children: [
          for (final c in Countries.all.where((c) => c.dialCode != null))
            ListTile(
              leading: CountryFlag(
                country: c,
                shape: FlagShape.rectangle,
                size: 20,
                borderRadius: BorderRadius.circular(3),
              ),
              title: Text(c.name),
              trailing: Text(c.dialCode!),
              onTap: () => Navigator.pop(context, c),
            ),
        ],
      ),
    );
    if (picked != null) {
      // Re-format what is already typed against the new country's mask.
      final digits = digitsOnly(_phone.text);
      setState(() {
        _country = picked;
        _phone.text = picked.formatPhoneNumber(digits);
      });
    }
  }

  String? get _error => switch (_country.validatePhoneNumber(_phone.text)) {
        PhoneNumberValidity.empty => null,
        PhoneNumberValidity.tooShort => 'Too short',
        PhoneNumberValidity.tooLong => 'Too long',
        PhoneNumberValidity.valid => null,
      };

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(
            controller: _phone,
            keyboardType: TextInputType.phone,
            inputFormatters: [PhoneInputFormatter.forCountry(_country)],
            onChanged: (_) => setState(() {}),
            decoration: InputDecoration(
              border: const OutlineInputBorder(),
              errorText: _error,
              hintText: _country.phoneMask ?? _country.phoneExample,
              prefixIcon: InkWell(
                onTap: _pickCountry,
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const SizedBox(width: 12),
                    CountryFlag(
                      country: _country,
                      shape: FlagShape.rectangle,
                      size: 18,
                      borderRadius: BorderRadius.circular(3),
                    ),
                    const SizedBox(width: 6),
                    Text(_country.dialCode ?? ''),
                    const Icon(Icons.arrow_drop_down),
                    const SizedBox(width: 4),
                  ],
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          Text('Example number: ${_country.phoneExample ?? '—'}\n'
              'Length: ${_country.phoneMinLength ?? '?'}–'
              '${_country.phoneMaxLength ?? '?'} digits\n'
              'Emoji flag: ${_country.flagEmoji}  ·  '
              'French name: ${_country.nameIn('fr')}'),
        ],
      ),
    );
  }
}

/// Searchable list of the extra non-ISO flags (Scotland, EU, UN, ...) —
/// [SpecialFlags.search] + [CountryFlag.fromCode]. Kept separate from the
/// country selector: these are not countries and never appear in
/// [Countries.search].
class SpecialFlagsDemo extends StatefulWidget {
  const SpecialFlagsDemo({super.key});

  @override
  State<SpecialFlagsDemo> createState() => _SpecialFlagsDemoState();
}

class _SpecialFlagsDemoState extends State<SpecialFlagsDemo> {
  final _search = TextEditingController();
  String _query = '';

  @override
  void dispose() {
    _search.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final flags = _query.isNotEmpty
        ? SpecialFlags.search(_query)
        : SpecialFlags.flags;
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(12),
          child: TextField(
            controller: _search,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.search),
              hintText: 'Search Scotland, Euskadi, EU…',
              border: OutlineInputBorder(),
            ),
            onChanged: (q) => setState(() => _query = q),
          ),
        ),
        Expanded(
          child: ListView.builder(
            itemCount: flags.length,
            itemBuilder: (context, i) {
              final flag = flags[i];
              final parent = flag.parentCode == null
                  ? null
                  : Countries.byAlpha2(flag.parentCode!);
              return ListTile(
                leading: CountryFlag.fromCode(
                  flag.code,
                  shape: FlagShape.rectangle,
                  size: 24,
                  borderRadius: BorderRadius.circular(3),
                ),
                title: Text(flag.name),
                subtitle: Text(
                  [
                    if (flag.nativeName != null) flag.nativeName!,
                    if (parent != null) 'part of ${parent.name}',
                  ].join(' · '),
                ),
                trailing: Text(flag.code),
              );
            },
          ),
        ),
      ],
    );
  }
}
0
likes
160
points
116
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Country data + flag widgets for building phone pickers. ISO codes, dial codes, currencies, localized names, phone masks, and SVG flags in circle, square, and rectangle shapes.

Repository (GitHub)
View/report issues

Topics

#country #flags #phone #picker #svg

License

MIT (license)

Dependencies

flutter, flutter_svg

More

Packages that depend on country_kit