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.
282 lines
11 KiB
Dart
282 lines
11 KiB
Dart
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';
|
|
import '../servicios/servicio_perfil_usuario.dart';
|
|
import '../tema/componentes_farolero.dart';
|
|
import '../tema/tema_app.dart';
|
|
|
|
class PantallaAjustes extends StatefulWidget {
|
|
const PantallaAjustes({super.key});
|
|
|
|
@override
|
|
State<PantallaAjustes> createState() => _PantallaAjustesState();
|
|
}
|
|
|
|
class _PantallaAjustesState extends State<PantallaAjustes> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final servicioIdioma = context.watch<ServicioIdioma>();
|
|
final perfil = context.watch<ServicioPerfilUsuario>().perfil;
|
|
final nombrePerfil = perfil.nombre.trim().isEmpty
|
|
? l10n.defaultPlayerName
|
|
: perfil.nombre.trim();
|
|
final inicialPerfil = nombrePerfil.substring(0, 1).toUpperCase();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(l10n.settingsTitle)),
|
|
body: FondoFarolero(
|
|
intenso: true,
|
|
respetarBarras: true,
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const ArteGameplayFarolero.ajustes(height: 142),
|
|
const SizedBox(height: 8),
|
|
Card(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(12),
|
|
onTap: () => _editarPerfil(context),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Row(
|
|
children: [
|
|
AvatarFarolero(
|
|
texto: inicialPerfil,
|
|
assetPath: perfil.avatarAsset,
|
|
size: 96,
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
nombrePerfil,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
Text(
|
|
'@${perfil.nick}',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Image.asset(
|
|
'assets/ui/generated/actions/action_settings_gear.webp',
|
|
width: 38,
|
|
height: 38,
|
|
fit: BoxFit.contain,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
l10n.language,
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
const SizedBox(height: 12),
|
|
_opcionIdioma(
|
|
context,
|
|
codigoBandera: 'sistema',
|
|
bandera: '\u{1F310}',
|
|
nombre: '${l10n.automaticLanguage} (${_nombreIdiomaDelSistema()})',
|
|
codigo: 'sistema',
|
|
seleccionado: servicioIdioma.codigoActual == 'sistema',
|
|
onTap: () => servicioIdioma.cambiarIdioma('sistema'),
|
|
),
|
|
const Divider(height: 1),
|
|
...ServicioIdioma.idiomasSoportados.entries.map((entrada) {
|
|
return _opcionIdioma(
|
|
context,
|
|
codigoBandera: entrada.key,
|
|
bandera: entrada.value.bandera,
|
|
nombre: entrada.value.nombre,
|
|
codigo: entrada.key,
|
|
seleccionado: servicioIdioma.codigoActual == entrada.key,
|
|
onTap: () => servicioIdioma.cambiarIdioma(entrada.key),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _opcionIdioma(
|
|
BuildContext context, {
|
|
required String codigoBandera,
|
|
required String bandera,
|
|
required String nombre,
|
|
required String codigo,
|
|
required bool seleccionado,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return ListTile(
|
|
leading: BanderaIdioma(codigo: codigoBandera, emoji: bandera),
|
|
title: Text(nombre),
|
|
trailing: seleccionado
|
|
? IconoFarolero(Icons.check_circle, color: TemaApp.colorAcento)
|
|
: null,
|
|
onTap: onTap,
|
|
dense: true,
|
|
selected: seleccionado,
|
|
selectedTileColor: TemaApp.colorAcento.withValues(alpha: 0.1),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
);
|
|
}
|
|
|
|
Future<void> _editarPerfil(BuildContext context) async {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final servicioPerfil = context.read<ServicioPerfilUsuario>();
|
|
final actual = servicioPerfil.perfil;
|
|
final nombreController = TextEditingController(text: actual.nombre);
|
|
final nickController = TextEditingController(text: actual.nick);
|
|
var avatarSeleccionado = actual.avatarAsset;
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => StatefulBuilder(
|
|
builder: (ctx, setDialogState) => AlertDialog(
|
|
title: Text(l10n.deviceProfile),
|
|
content: SizedBox(
|
|
width: 520,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const ArteGameplayFarolero.ajustes(height: 118),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: nombreController,
|
|
textCapitalization: TextCapitalization.words,
|
|
onChanged: (_) => setDialogState(() {}),
|
|
decoration: InputDecoration(
|
|
labelText: l10n.profileName,
|
|
prefixIcon: IconoFarolero(Icons.person),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: nickController,
|
|
decoration: InputDecoration(
|
|
labelText: l10n.profileNick,
|
|
prefixIcon: IconoFarolero(Icons.alternate_email),
|
|
),
|
|
),
|
|
const SizedBox(height: 18),
|
|
AvatarFarolero(
|
|
texto: nombreController.text.isEmpty
|
|
? '?'
|
|
: nombreController.text.substring(0, 1).toUpperCase(),
|
|
assetPath: avatarSeleccionado,
|
|
size: 112,
|
|
),
|
|
const SizedBox(height: 18),
|
|
LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final columnas = constraints.maxWidth >= 420 ? 4 : 3;
|
|
return GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: columnas,
|
|
mainAxisSpacing: 14,
|
|
crossAxisSpacing: 14,
|
|
),
|
|
itemCount: ServicioPerfilUsuario.avatares.length,
|
|
itemBuilder: (context, index) {
|
|
final avatar = ServicioPerfilUsuario.avatares[index];
|
|
final seleccionado = avatar == avatarSeleccionado;
|
|
return InkWell(
|
|
borderRadius: BorderRadius.circular(999),
|
|
onTap: () => setDialogState(
|
|
() => avatarSeleccionado = avatar,
|
|
),
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: seleccionado
|
|
? TemaApp.colorNaranja
|
|
: Colors.transparent,
|
|
width: 3,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: AvatarFarolero(
|
|
texto: '',
|
|
assetPath: avatar,
|
|
size: 86,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: Text(l10n.cancel),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
await servicioPerfil.guardar(
|
|
nombre: nombreController.text,
|
|
nick: nickController.text,
|
|
avatarAsset: avatarSeleccionado,
|
|
);
|
|
if (ctx.mounted) Navigator.pop(ctx);
|
|
},
|
|
child: Text(l10n.save),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
nombreController.dispose();
|
|
nickController.dispose();
|
|
}
|
|
|
|
String _nombreIdiomaDelSistema() {
|
|
final locale = WidgetsBinding.instance.platformDispatcher.locale;
|
|
final codigo = locale.countryCode != null && locale.countryCode!.isNotEmpty
|
|
? '${locale.languageCode}_${locale.countryCode}'
|
|
: locale.languageCode;
|
|
final info = ServicioIdioma.idiomasSoportados[codigo] ??
|
|
ServicioIdioma.idiomasSoportados[locale.languageCode];
|
|
return info?.nombre ?? locale.languageCode;
|
|
}
|
|
}
|