138 lines
4.7 KiB
Dart
138 lines
4.7 KiB
Dart
/*
|
|
* Copyright (c) 2024-2026 Fabian Freund.
|
|
*
|
|
* This file is part of WebLibre
|
|
* (see https://weblibre.eu).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import 'package:country_codes/country_codes.dart';
|
|
import 'package:country_flags/country_flags.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
/// Sentinel value returned when the user selects "Automatic" (no country).
|
|
/// Distinguished from `null` which means the user navigated back without
|
|
/// making a selection.
|
|
const automaticCountry = '';
|
|
|
|
class CountryPickerScreen extends HookWidget {
|
|
const CountryPickerScreen({
|
|
super.key,
|
|
required this.title,
|
|
this.selectedCountryCode,
|
|
});
|
|
|
|
final String title;
|
|
final String? selectedCountryCode;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final searchController = useTextEditingController();
|
|
final searchQuery = useState('');
|
|
|
|
final countries = useMemoized(() {
|
|
return CountryCodes.countryCodes().map((country) {
|
|
final label =
|
|
country.localizedName ??
|
|
country.name ??
|
|
country.alpha2Code ??
|
|
country.countryCode ??
|
|
'Unnamed Country';
|
|
return (alpha2Code: country.alpha2Code, label: label);
|
|
}).toList()..sort((a, b) => a.label.compareTo(b.label));
|
|
});
|
|
|
|
final filteredCountries = useMemoized(() {
|
|
if (searchQuery.value.isEmpty) return countries;
|
|
final query = searchQuery.value.toLowerCase();
|
|
return countries
|
|
.where((c) => c.label.toLowerCase().contains(query))
|
|
.toList();
|
|
}, [searchQuery.value, countries]);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(title),
|
|
bottom: PreferredSize(
|
|
preferredSize: const Size.fromHeight(56),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16.0,
|
|
vertical: 8.0,
|
|
),
|
|
child: TextField(
|
|
controller: searchController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Search countries...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: searchQuery.value.isNotEmpty
|
|
? IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
searchController.clear();
|
|
searchQuery.value = '';
|
|
},
|
|
)
|
|
: null,
|
|
filled: true,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: EdgeInsets.zero,
|
|
),
|
|
onChanged: (value) => searchQuery.value = value,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: ListView.builder(
|
|
itemCount: filteredCountries.length + 1,
|
|
itemBuilder: (context, index) {
|
|
if (index == 0) {
|
|
final isSelected = selectedCountryCode == null;
|
|
return ListTile(
|
|
leading: const SizedBox(
|
|
width: 32,
|
|
height: 24,
|
|
child: Center(child: Icon(Icons.public)),
|
|
),
|
|
title: const Text('Automatic'),
|
|
trailing: isSelected ? const Icon(Icons.check) : null,
|
|
onTap: () => context.pop(automaticCountry),
|
|
);
|
|
}
|
|
|
|
final country = filteredCountries[index - 1];
|
|
final isSelected = country.alpha2Code == selectedCountryCode;
|
|
|
|
return ListTile(
|
|
leading: country.alpha2Code != null
|
|
? CountryFlag.fromCountryCode(
|
|
country.alpha2Code!,
|
|
theme: const EmojiTheme(size: 28),
|
|
)
|
|
: const SizedBox(width: 32),
|
|
title: Text(country.label),
|
|
trailing: isSelected ? const Icon(Icons.check) : null,
|
|
onTap: () => context.pop(country.alpha2Code),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|