From b551c85edbd61250c83d144efad7b3da1c5ce0da Mon Sep 17 00:00:00 2001 From: freetlab Date: Sun, 26 Jul 2026 00:17:59 +0200 Subject: [PATCH] feat(i18n): draw the Catalan and Basque flags in the language picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every language showed its flag except those two, which fell back to a plain black flag. Unicode does define subdivision sequences for both, but outside England, Scotland and Wales they are not in the recommended set, so almost no system renders them. Both flags are simple geometry, so they are painted rather than shipped as images: crisp at any size, no new assets, no licensing question. The senyera is nine equal stripes; the ikurriña is the green saltire under the white cross on red. The rest keep their emoji, now boxed to the same size so the list lines up. Not seen on a device: verified by build and analysis only. --- lib/pantallas/pantalla_ajustes.dart | 6 +- lib/tema/banderas_idioma.dart | 128 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 lib/tema/banderas_idioma.dart diff --git a/lib/pantallas/pantalla_ajustes.dart b/lib/pantallas/pantalla_ajustes.dart index 0035f77..89f253d 100644 --- a/lib/pantallas/pantalla_ajustes.dart +++ b/lib/pantallas/pantalla_ajustes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../tema/banderas_idioma.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../servicios/servicio_idioma.dart'; @@ -95,6 +96,7 @@ class _PantallaAjustesState extends State { const SizedBox(height: 12), _opcionIdioma( context, + codigoBandera: 'sistema', bandera: '\u{1F310}', nombre: '${l10n.automaticLanguage} (${_nombreIdiomaDelSistema()})', codigo: 'sistema', @@ -105,6 +107,7 @@ class _PantallaAjustesState extends State { ...ServicioIdioma.idiomasSoportados.entries.map((entrada) { return _opcionIdioma( context, + codigoBandera: entrada.key, bandera: entrada.value.bandera, nombre: entrada.value.nombre, codigo: entrada.key, @@ -126,6 +129,7 @@ class _PantallaAjustesState extends State { Widget _opcionIdioma( BuildContext context, { + required String codigoBandera, required String bandera, required String nombre, required String codigo, @@ -133,7 +137,7 @@ class _PantallaAjustesState extends State { required VoidCallback onTap, }) { return ListTile( - leading: Text(bandera, style: const TextStyle(fontSize: 24)), + leading: BanderaIdioma(codigo: codigoBandera, emoji: bandera), title: Text(nombre), trailing: seleccionado ? IconoFarolero(Icons.check_circle, color: TemaApp.colorAcento) diff --git a/lib/tema/banderas_idioma.dart b/lib/tema/banderas_idioma.dart new file mode 100644 index 0000000..5ea0941 --- /dev/null +++ b/lib/tema/banderas_idioma.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; + +/// Bandera del selector de idioma. +/// +/// La mayoría son emoji de indicador regional y el sistema los dibuja solo. +/// Catalán y euskera no: Unicode define sus secuencias de subdivisión, pero +/// fuera de Inglaterra, Escocia y Gales no están en el conjunto recomendado, +/// así que casi ningún sistema las representa y salía una bandera negra. Esas +/// dos se pintan aquí; son geometría simple y así quedan nítidas a cualquier +/// tamaño sin añadir imágenes. +class BanderaIdioma extends StatelessWidget { + final String codigo; + final String emoji; + final double alto; + + const BanderaIdioma({ + super.key, + required this.codigo, + required this.emoji, + this.alto = 22, + }); + + static const _pintadas = {'ca', 'eu'}; + + /// True si la bandera de ese idioma se dibuja en vez de usar emoji. + static bool sePinta(String codigo) => _pintadas.contains(codigo); + + @override + Widget build(BuildContext context) { + if (!sePinta(codigo)) { + // El emoji ocupa lo que ocupe; se le da la misma caja que a las pintadas + // para que la lista quede alineada. + return SizedBox( + width: alto * 1.5, + height: alto, + child: Center( + child: Text(emoji, style: TextStyle(fontSize: alto * 1.1)), + ), + ); + } + + return SizedBox( + width: alto * 1.5, + height: alto, + child: ClipRRect( + borderRadius: BorderRadius.circular(3), + child: CustomPaint( + painter: codigo == 'ca' + ? const _SenyeraPainter() + : const _IkurrinaPainter(), + size: Size(alto * 1.5, alto), + ), + ), + ); + } +} + +/// Senyera: cuatro palos rojos sobre fondo amarillo, nueve franjas iguales. +class _SenyeraPainter extends CustomPainter { + const _SenyeraPainter(); + + static const _amarillo = Color(0xFFFCDD09); + static const _rojo = Color(0xFFDA121A); + + @override + void paint(Canvas canvas, Size size) { + canvas.drawRect( + Rect.fromLTWH(0, 0, size.width, size.height), + Paint()..color = _amarillo, + ); + + final franja = size.height / 9; + final pincel = Paint()..color = _rojo; + // Las franjas impares (1, 3, 5, 7) son los cuatro palos. + for (var i = 1; i < 9; i += 2) { + canvas.drawRect( + Rect.fromLTWH(0, franja * i, size.width, franja), + pincel, + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +/// Ikurriña: fondo rojo, aspa verde y cruz blanca encima. +class _IkurrinaPainter extends CustomPainter { + const _IkurrinaPainter(); + + static const _rojo = Color(0xFFD52B1E); + static const _verde = Color(0xFF009B48); + static const _blanco = Color(0xFFFFFFFF); + + @override + void paint(Canvas canvas, Size size) { + final w = size.width; + final h = size.height; + + canvas.drawRect(Rect.fromLTWH(0, 0, w, h), Paint()..color = _rojo); + + // El aspa va de esquina a esquina, por debajo de la cruz. + final grosor = h * 0.19; + final aspa = Paint() + ..color = _verde + ..strokeWidth = grosor + ..style = PaintingStyle.stroke; + canvas.save(); + canvas.clipRect(Rect.fromLTWH(0, 0, w, h)); + canvas.drawLine(Offset.zero, Offset(w, h), aspa); + canvas.drawLine(Offset(w, 0), Offset(0, h), aspa); + canvas.restore(); + + // Cruz blanca centrada, encima del aspa. + final blanco = Paint()..color = _blanco; + canvas.drawRect( + Rect.fromLTWH((w - grosor) / 2, 0, grosor, h), + blanco, + ); + canvas.drawRect( + Rect.fromLTWH(0, (h - grosor) / 2, w, grosor), + blanco, + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +}