v0.2.0: i18n 18 idiomas + pantalla ajustes + bancos multiidioma
Internacionalización completa: - 18 ficheros .arb: es, en, fr, pt, de, it, ru, ja, ko, zh, zh_TW, ar, hi, tr, pl, nl, ca, eu - Todos los strings extraídos de todas las pantallas - Detección automática de idioma del sistema - Selector manual en pantalla de ajustes Pantalla de ajustes nueva: - Selector de idioma con banderas emoji - Vibración ON/OFF - Acerca de (versión, desarrollador) Bancos de palabras multiidioma: - palabras.json (castellano, 1000 palabras) - palabras_en.json (inglés, 1000 palabras) - palabras_fr.json (francés, 1000 palabras) - Fallback a castellano si no hay banco del idioma 13138 líneas Dart, 39 ficheros, 0 issues en flutter analyze
This commit is contained in:
182
lib/pantallas/pantalla_ajustes.dart
Normal file
182
lib/pantallas/pantalla_ajustes.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../servicios/servicio_idioma.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
|
||||
class PantallaAjustes extends StatefulWidget {
|
||||
const PantallaAjustes({super.key});
|
||||
|
||||
@override
|
||||
State<PantallaAjustes> createState() => _PantallaAjustesState();
|
||||
}
|
||||
|
||||
class _PantallaAjustesState extends State<PantallaAjustes> {
|
||||
double _volumen = 0.7;
|
||||
bool _vibracion = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final servicioIdioma = context.watch<ServicioIdioma>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.settingsTitle)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Selector de idioma
|
||||
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),
|
||||
// Opción automática (sistema)
|
||||
_opcionIdioma(
|
||||
context,
|
||||
bandera: '🌐',
|
||||
nombre: 'Auto (${_nombreIdiomaDelSistema()})',
|
||||
codigo: 'sistema',
|
||||
seleccionado: servicioIdioma.codigoActual == 'sistema',
|
||||
onTap: () => servicioIdioma.cambiarIdioma('sistema'),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
// Lista de idiomas
|
||||
...ServicioIdioma.idiomasSoportados.entries.map((entrada) {
|
||||
return _opcionIdioma(
|
||||
context,
|
||||
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),
|
||||
|
||||
// Volumen de efectos de sonido
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.soundVolume,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 8),
|
||||
Slider(
|
||||
value: _volumen,
|
||||
onChanged: (v) => setState(() => _volumen = v),
|
||||
activeColor: TemaApp.colorAcento,
|
||||
inactiveColor: TemaApp.colorTarjeta,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Vibración
|
||||
Card(
|
||||
child: SwitchListTile(
|
||||
title: Text(l10n.vibration),
|
||||
value: _vibracion,
|
||||
onChanged: (v) => setState(() => _vibracion = v),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Acerca de
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.about,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
_filaInfo(context, l10n.version, '1.0.0'),
|
||||
const SizedBox(height: 8),
|
||||
_filaInfo(context, l10n.developer, 'FreeTTimeLab'),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: 'El Impostor',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
},
|
||||
child: Text(l10n.licenses),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _opcionIdioma(
|
||||
BuildContext context, {
|
||||
required String bandera,
|
||||
required String nombre,
|
||||
required String codigo,
|
||||
required bool seleccionado,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
leading: Text(bandera, style: const TextStyle(fontSize: 24)),
|
||||
title: Text(nombre),
|
||||
trailing: seleccionado
|
||||
? const Icon(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)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _filaInfo(BuildContext context, String etiqueta, String valor) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(etiqueta, style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text(valor, style: Theme.of(context).textTheme.bodyLarge),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user