feat(i18n): draw the Catalan and Basque flags in the language picker
Build & Deploy Farolero / Análisis de código (push) Successful in 11s
Build & Deploy Farolero / Build APK + AAB release (push) Successful in 1m18s

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.
This commit is contained in:
2026-07-26 00:18:06 +02:00
parent 0a6ac6cba0
commit b551c85edb
2 changed files with 133 additions and 1 deletions
+5 -1
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../tema/banderas_idioma.dart';
import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../servicios/servicio_idioma.dart'; import '../servicios/servicio_idioma.dart';
@@ -95,6 +96,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
const SizedBox(height: 12), const SizedBox(height: 12),
_opcionIdioma( _opcionIdioma(
context, context,
codigoBandera: 'sistema',
bandera: '\u{1F310}', bandera: '\u{1F310}',
nombre: '${l10n.automaticLanguage} (${_nombreIdiomaDelSistema()})', nombre: '${l10n.automaticLanguage} (${_nombreIdiomaDelSistema()})',
codigo: 'sistema', codigo: 'sistema',
@@ -105,6 +107,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
...ServicioIdioma.idiomasSoportados.entries.map((entrada) { ...ServicioIdioma.idiomasSoportados.entries.map((entrada) {
return _opcionIdioma( return _opcionIdioma(
context, context,
codigoBandera: entrada.key,
bandera: entrada.value.bandera, bandera: entrada.value.bandera,
nombre: entrada.value.nombre, nombre: entrada.value.nombre,
codigo: entrada.key, codigo: entrada.key,
@@ -126,6 +129,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
Widget _opcionIdioma( Widget _opcionIdioma(
BuildContext context, { BuildContext context, {
required String codigoBandera,
required String bandera, required String bandera,
required String nombre, required String nombre,
required String codigo, required String codigo,
@@ -133,7 +137,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
return ListTile( return ListTile(
leading: Text(bandera, style: const TextStyle(fontSize: 24)), leading: BanderaIdioma(codigo: codigoBandera, emoji: bandera),
title: Text(nombre), title: Text(nombre),
trailing: seleccionado trailing: seleccionado
? IconoFarolero(Icons.check_circle, color: TemaApp.colorAcento) ? IconoFarolero(Icons.check_circle, color: TemaApp.colorAcento)
+128
View File
@@ -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;
}