/* * 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 . */ import 'package:country_flags/country_flags.dart'; import 'package:fast_equatable/fast_equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/locale.dart'; import 'package:nullability/nullability.dart'; import 'package:weblibre/domain/repositories/locale_resolver.dart'; import 'package:weblibre/extensions/locale.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/user/data/models/engine_settings.dart'; import 'package:weblibre/features/user/domain/repositories/engine_settings.dart'; class LocaleSettingsScreen extends HookConsumerWidget { const LocaleSettingsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final formKey = useMemoized(() => GlobalKey()); final systemLocales = useMemoized( () => WidgetsBinding.instance.platformDispatcher.locales .map((locale) => locale.toIntlLocale()) .toList(), ); final userLocales = ref.watch( engineSettingsWithDefaultsProvider.select( (settings) => EquatableValue( settings.locales.map(Locale.tryParse).nonNulls.toSet(), ), ), ); final availableLocales = { ...systemLocales, ...userLocales.value, Locale.fromSubtags(languageCode: 'en', countryCode: 'US'), }; final customLocaleController = useTextEditingController(); return Scaffold( appBar: AppBar(title: const Text('Browser Languages')), body: SafeArea( child: ListView( children: [ ...availableLocales.map((locale) { return CheckboxListTile.adaptive( value: userLocales.value.contains(locale), onChanged: (value) async { if (value != null) { await ref .read(saveEngineSettingsControllerProvider.notifier) .save( (currentSettings) => currentSettings.copyWith.locales( value ? { ...currentSettings.locales, locale.toLanguageTag(), }.toList() : ([ ...currentSettings.locales, ]..remove(locale.toLanguageTag())).toList(), ), ); } }, title: Consumer( builder: (context, ref, child) { final resolvedAsync = ref.watch( resolveLocaleProvider(locale), ); return Text( resolvedAsync.maybeWhen( data: (data) => data.mapNotNull( (data) => '${data.languageName} ${data.countryName.mapNotNull((country) => '($country)') ?? ''}' .trim(), ) ?? locale.toLanguageTag(), orElse: () => locale.toLanguageTag(), ), ); }, ), subtitle: Text(locale.toLanguageTag()), secondary: CountryFlag.fromLanguageCode( locale.languageCode, theme: const ImageTheme(shape: RoundedRectangle(8.0)), ), ); }), const Divider(), Padding( padding: const EdgeInsets.only(left: 16.0, right: 20), child: Form( key: formKey, child: TextFormField( controller: customLocaleController, decoration: InputDecoration( label: const Text('Custom Locale'), hint: const Text('en-US'), floatingLabelBehavior: FloatingLabelBehavior.always, suffixIcon: IconButton( onPressed: () { if (formKey.currentState?.validate() == true) { formKey.currentState?.save(); } }, icon: const Icon(Icons.add), ), ), validator: (value) { if (value != null && Locale.tryParse(value) == null) { return 'Invalid locale identifier'; } return null; }, onSaved: (newValue) async { if (newValue != null) { await ref .read(saveEngineSettingsControllerProvider.notifier) .save( (currentSettings) => currentSettings.copyWith.locales( { ...currentSettings.locales, Locale.parse(newValue).toLanguageTag(), }.toList(), ), ); customLocaleController.clear(); } }, ), ), ), ], ), ), ); } }