fix: completar migracion i18n de literales visibles
Build & Deploy PluriWave / Análisis de código (push) Failing after 28s
Build & Deploy PluriWave / Build APK + AAB release (push) Has been skipped

This commit is contained in:
Javier Bautista Fernández
2026-06-03 13:43:13 +02:00
parent 7abc8c3b0f
commit 643ba1eb45
20 changed files with 1572 additions and 635 deletions
+136 -144
View File
@@ -3,6 +3,8 @@ import 'package:provider/provider.dart';
import '../estado/estado_alarmas.dart';
import '../estado/estado_radio.dart';
import '../l10n/app_localizations_ext.dart';
import '../l10n/gen/app_localizations.dart';
import '../modelos/alarma_musical.dart';
import '../modelos/emisora.dart';
import '../widgets/pluri_glass_surface.dart';
@@ -16,6 +18,7 @@ class PantallaAlarmas extends StatelessWidget {
@override
Widget build(BuildContext context) {
final estado = context.watch<EstadoAlarmas>();
final l10n = AppLocalizations.of(context);
return RefreshIndicator(
onRefresh: estado.refrescarProgramacion,
@@ -23,15 +26,14 @@ class PantallaAlarmas extends StatelessWidget {
padding: PluriLayout.pageListPadding,
children: [
PluriScreenHeader(
title: 'Despertar musical',
subtitle:
'Alarmas con radio, sonido seguro, vacaciones inteligentes y próxima ejecución siempre visible.',
title: l10n.alarmScreenTitle,
subtitle: l10n.alarmScreenSubtitle,
glyph: PluriIconGlyph.alarm,
primaryActionLabel: 'Crear alarma',
primaryActionLabel: l10n.createAlarmAction,
onPrimaryAction: () => _abrirEditor(context),
trailing: PluriStatusPill(
icon: Icons.alarm_on_rounded,
label: '${estado.alarmas.length} alarmas',
label: l10n.alarmsCount(estado.alarmas.length),
),
),
Padding(
@@ -79,12 +81,13 @@ class _PanelProximaAlarma extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final proxima = estado.proximaAlarma;
final activasSinProxima =
estado.alarmas
.where((a) => a.activa && a.proximaProgramable == null)
.length;
final activasSinProxima = estado.alarmas
.where((a) => a.activa && a.proximaProgramable == null)
.length;
final proximaProgramable = proxima?.proximaProgramable;
return PluriGlassSurface(
glowColor: const Color(0xFFFFB86B).withValues(alpha: 0.28),
child: Row(
@@ -98,20 +101,25 @@ class _PanelProximaAlarma extends StatelessWidget {
Text(
proxima == null
? activasSinProxima > 0
? 'Alarmas activas sin próxima ejecución'
: 'Sin alarmas activas'
: 'Próxima alarma',
? l10n.activeAlarmsWithoutNextTitle
: l10n.activeAlarmsNoneTitle
: l10n.nextAlarmTitle,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 4),
Text(
proxima == null
? activasSinProxima > 0
? 'Hay $activasSinProxima alarma(s) activas, pero ahora mismo no tienen una fecha futura válida. Revisá fecha, días y vacaciones.'
: 'Creá una alarma y PluriWave calculará la siguiente ejecución automáticamente.'
: '${proxima.nombre} · ${_fechaHora(proximaProgramable!)}',
? l10n.activeAlarmsWithoutNextSubtitle(
activasSinProxima,
)
: l10n.createAlarmHint
: l10n.alarmNextSummary(
proxima.nombre,
_fechaHora(l10n, proximaProgramable!),
),
),
],
),
@@ -129,9 +137,10 @@ class _TarjetaAlarma extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final estado = context.watch<EstadoAlarmas>();
final excepcion = estado.ultimaExcepcionPara(alarma.id);
final mensajeVacaciones = _mensajeVacaciones(estado.vacaciones);
final mensajeVacaciones = _mensajeVacaciones(l10n, estado.vacaciones);
return PluriGlassSurface(
glowColor: const Color(0xFF22D3EE).withValues(alpha: 0.22),
child: Column(
@@ -174,14 +183,14 @@ class _TarjetaAlarma extends StatelessWidget {
children: [
_InfoChip(
icon: Icons.repeat_rounded,
label: _programacion(alarma),
label: _programacion(l10n, alarma),
),
_InfoChip(
icon: Icons.beach_access_rounded,
label:
alarma.sonarEnVacaciones
? 'Suena en vacaciones'
: 'Pausa en vacaciones',
? l10n.alarmVacationPlay
: l10n.alarmVacationPause,
),
_InfoChip(
icon: Icons.volume_up_rounded,
@@ -189,7 +198,7 @@ class _TarjetaAlarma extends StatelessWidget {
),
_InfoChip(
icon: Icons.trending_up_rounded,
label: 'Fade-in ${alarma.fadeInSegundos}s',
label: l10n.fadeInSeconds(alarma.fadeInSegundos),
),
],
),
@@ -197,20 +206,22 @@ class _TarjetaAlarma extends StatelessWidget {
if (alarma.proximaProgramable != null)
_NoticeLine(
icon: Icons.event_available_rounded,
text:
'Siguiente ejecución: ${_fechaHora(alarma.proximaProgramable!)}',
text: l10n.alarmNextExecution(
_fechaHora(l10n, alarma.proximaProgramable!),
),
)
else
const _NoticeLine(
_NoticeLine(
icon: Icons.pause_circle_outline_rounded,
text: 'No tiene próxima ejecución activa.',
text: l10n.alarmNoNextExecution,
),
if (excepcion != null) ...[
const SizedBox(height: 6),
_NoticeLine(
icon: Icons.skip_next_rounded,
text:
'Una ejecución fue omitida: ${_fechaHora(excepcion.ejecucion)}.',
text: l10n.alarmSkippedExecution(
_fechaHora(l10n, excepcion.ejecucion),
),
),
],
if (mensajeVacaciones != null) ...[
@@ -225,12 +236,12 @@ class _TarjetaAlarma extends StatelessWidget {
children: [
TextButton.icon(
icon: const Icon(Icons.edit_rounded),
label: const Text('Editar'),
label: Text(l10n.editAction),
onPressed: () => _abrirEditor(context, alarma: alarma),
),
TextButton.icon(
icon: const Icon(Icons.skip_next_rounded),
label: const Text('Omitir siguiente'),
label: Text(l10n.skipNextAction),
onPressed:
alarma.proximaProgramable == null
? null
@@ -250,8 +261,13 @@ class _TarjetaAlarma extends StatelessWidget {
SnackBar(
content: Text(
actualizada?.proximaProgramable == null
? 'Alarma omitida. No queda próxima ejecución.'
: 'Alarma omitida. Volverá el ${_fechaHora(actualizada!.proximaProgramable!)}.',
? l10n.alarmSkippedNoNextSnackbar
: l10n.alarmSkippedReturnsSnackbar(
_fechaHora(
l10n,
actualizada!.proximaProgramable!,
),
),
),
),
);
@@ -260,7 +276,7 @@ class _TarjetaAlarma extends StatelessWidget {
),
const Spacer(),
IconButton(
tooltip: 'Eliminar',
tooltip: l10n.deleteAction,
icon: const Icon(Icons.delete_outline_rounded),
onPressed: () => estado.eliminarAlarma(alarma.id),
),
@@ -271,7 +287,10 @@ class _TarjetaAlarma extends StatelessWidget {
);
}
String? _mensajeVacaciones(List<RangoVacaciones> vacaciones) {
String? _mensajeVacaciones(
AppLocalizations l10n,
List<RangoVacaciones> vacaciones,
) {
if (alarma.sonarEnVacaciones) return null;
final ahora = DateTime.now();
RangoVacaciones? actual;
@@ -283,12 +302,17 @@ class _TarjetaAlarma extends StatelessWidget {
}
if (actual != null) {
if (alarma.proximaProgramable == null) {
return 'Está pausada por vacaciones (${actual.nombre}) y sin próxima ejecución.';
return l10n.alarmVacationPausedNoNext(actual.nombre);
}
return 'Está pausada por vacaciones (${actual.nombre}) y vuelve el ${_fechaHora(alarma.proximaProgramable!)}.';
return l10n.alarmVacationPausedReturns(
actual.nombre,
_fechaHora(l10n, alarma.proximaProgramable!),
);
}
if (alarma.proximaProgramable != null) {
return 'Con vacaciones activas, volverá a sonar el ${_fechaHora(alarma.proximaProgramable!)}.';
return l10n.alarmVacationReturns(
_fechaHora(l10n, alarma.proximaProgramable!),
);
}
return null;
}
@@ -330,9 +354,10 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
void initState() {
super.initState();
final alarma = widget.alarma;
final l10n = AppLocalizations.of(context);
final ahora = DateTime.now().add(const Duration(minutes: 5));
_nombreController = TextEditingController(
text: alarma?.nombre ?? 'Despertador musical',
text: alarma?.nombre ?? l10n.defaultAlarmName,
);
_hora = TimeOfDay(
hour: alarma?.hora ?? ahora.hour,
@@ -356,6 +381,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final radio = context.watch<EstadoRadio>();
final bottom = MediaQuery.of(context).viewInsets.bottom;
if (!_favoritosSolicitados) {
@@ -393,7 +419,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(width: 12),
Expanded(
child: Text(
widget.alarma == null ? 'Nueva alarma' : 'Editar alarma',
widget.alarma == null ? l10n.newAlarmTitle : l10n.editAlarmTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w900,
),
@@ -408,7 +434,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(height: 14),
TextField(
controller: _nombreController,
decoration: const InputDecoration(labelText: 'Nombre'),
decoration: InputDecoration(labelText: l10n.nameLabel),
),
const SizedBox(height: 12),
Row(
@@ -416,7 +442,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Expanded(
child: _PickerButton(
icon: Icons.schedule_rounded,
label: 'Hora',
label: l10n.timeLabel,
value: _hora.format(context),
onTap: _elegirHora,
),
@@ -425,7 +451,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Expanded(
child: _PickerButton(
icon: Icons.event_rounded,
label: 'Fecha',
label: l10n.dateLabel,
value: _fechaCorta(_fecha),
onTap:
_tipo == TipoProgramacionAlarma.unica
@@ -437,18 +463,18 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
),
const SizedBox(height: 12),
SegmentedButton<TipoProgramacionAlarma>(
segments: const [
segments: [
ButtonSegment(
value: TipoProgramacionAlarma.unica,
label: Text('Una vez'),
label: Text(l10n.oneTimeOption),
),
ButtonSegment(
value: TipoProgramacionAlarma.diaria,
label: Text('Diaria'),
label: Text(l10n.dailyOption),
),
ButtonSegment(
value: TipoProgramacionAlarma.diasSemana,
label: Text('Días'),
label: Text(l10n.weekdaysOption),
),
],
selected: {_tipo},
@@ -462,7 +488,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
children: [
for (var i = DateTime.monday; i <= DateTime.sunday; i++)
FilterChip(
label: Text(_diaCorto(i)),
label: Text(l10n.weekdayShort(i)),
selected: _diasSemana.contains(i),
onSelected:
(selected) => setState(() {
@@ -477,7 +503,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(height: 14),
_SectionLabel(
icon: 'assets/icons/alarmas/fallback_sound.png',
text: 'Sonido y volumen',
text: l10n.soundAndVolumeTitle,
),
Slider(
value: _volumen,
@@ -490,11 +516,11 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Fade-in de alarma'),
title: Text(l10n.alarmFadeInTitle),
subtitle: Text(
_fadeInSegundos == 0
? '0 s (sin transición)'
: '$_fadeInSegundos s (de 5% al volumen elegido)',
? l10n.alarmFadeInOff
: l10n.alarmFadeInProgress(_fadeInSegundos),
),
),
Slider(
@@ -509,21 +535,21 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
),
DropdownButtonFormField<SonidoInternoAlarma>(
initialValue: _sonidoInterno,
decoration: const InputDecoration(
labelText: 'Sonido seguro interno',
decoration: InputDecoration(
labelText: l10n.soundInternalSafe,
),
items: const [
items: [
DropdownMenuItem(
value: SonidoInternoAlarma.amanecer,
child: Text('Amanecer cálido'),
child: Text(l10n.soundWarmSunrise),
),
DropdownMenuItem(
value: SonidoInternoAlarma.campanaSuave,
child: Text('Campana suave'),
child: Text(l10n.soundSoftBell),
),
DropdownMenuItem(
value: SonidoInternoAlarma.pulsoDigital,
child: Text('Pulso digital'),
child: Text(l10n.soundDigitalPulse),
),
],
onChanged:
@@ -535,14 +561,14 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
DropdownButtonFormField<String>(
key: ValueKey(_emisora?.uuid ?? 'sin-emisora'),
initialValue: _emisora?.uuid,
decoration: const InputDecoration(
labelText: 'Emisora favorita',
prefixIcon: Icon(Icons.radio_rounded),
decoration: InputDecoration(
labelText: l10n.favoriteStationLabel,
prefixIcon: const Icon(Icons.radio_rounded),
),
items: [
const DropdownMenuItem<String>(
DropdownMenuItem<String>(
value: '',
child: Text('Sin emisora: usar sonido interno'),
child: Text(l10n.noStationUseInternalSound),
),
for (final emisora in favoritas)
DropdownMenuItem<String>(
@@ -564,9 +590,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
),
if (favoritas.isEmpty) ...[
const SizedBox(height: 6),
const Text(
'Guardá emisoras en Favoritos para usarlas como alarma musical.',
),
Text(l10n.saveFavoritesAlarmHint),
],
if (radio.emisoraActual != null) ...[
const SizedBox(height: 8),
@@ -576,7 +600,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
onPressed:
() => setState(() => _emisora = radio.emisoraActual),
icon: const Icon(Icons.add_task_rounded),
label: const Text('Usar emisora actual'),
label: Text(l10n.useCurrentStationAction),
),
),
],
@@ -584,22 +608,19 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
value: _sonarEnVacaciones,
onChanged:
(value) => setState(() => _sonarEnVacaciones = value),
onChanged: (value) => setState(() => _sonarEnVacaciones = value),
secondary: const _AssetIcon(
'assets/icons/alarmas/vacation_wave.png',
size: 42,
),
title: const Text('Sonar durante vacaciones'),
subtitle: const Text(
'Si lo apagás, la próxima ejecución saltará al primer día válido.',
),
title: Text(l10n.playDuringVacations),
subtitle: Text(l10n.playDuringVacationsHint),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _guardar,
icon: const Icon(Icons.check_rounded),
label: const Text('Guardar alarma'),
label: Text(l10n.saveAlarmAction),
),
],
),
@@ -627,7 +648,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Future<void> _guardar() async {
if (_tipo == TipoProgramacionAlarma.diasSemana && _diasSemana.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Elegí al menos un día de la semana.')),
SnackBar(content: Text(AppLocalizations.of(context).chooseOneWeekdayError)),
);
return;
}
@@ -645,7 +666,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
.copyWith(
nombre:
_nombreController.text.trim().isEmpty
? 'Despertador musical'
? AppLocalizations.of(context).defaultAlarmName
: _nombreController.text.trim(),
hora: _hora.hour,
minuto: _hora.minute,
@@ -688,7 +709,11 @@ class _AccesoDiagnostico extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final diag = estado.diagnostico;
final exactStatus = diag?.puedeProgramarExactas == true ? l10n.statusOk : l10n.statusPending;
final notificationStatus = diag?.notificacionesPermitidas == true ? l10n.statusOk : l10n.statusPending;
final screenStatus = diag?.puedeUsarPantallaCompleta == true ? l10n.statusOk : l10n.statusPending;
return TextButton.icon(
icon: const _AssetIcon(
'assets/icons/alarmas/android_reliability.png',
@@ -696,8 +721,12 @@ class _AccesoDiagnostico extends StatelessWidget {
),
label: Text(
diag == null
? 'Revisar fiabilidad Android'
: 'Fiabilidad: exactas ${diag.puedeProgramarExactas ? 'OK' : 'pendiente'} ? notificaciones ${diag.notificacionesPermitidas ? 'OK' : 'pendiente'} ? pantalla ${diag.puedeUsarPantallaCompleta ? 'OK' : 'pendiente'}',
? l10n.androidReliabilityTitle
: l10n.androidReliabilityStatus(
exactStatus,
notificationStatus,
screenStatus,
),
),
onPressed: () async {
if (diag != null && !diag.puedeProgramarExactas) {
@@ -722,6 +751,7 @@ class _PanelVacaciones extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final vacaciones = [...estado.vacaciones]
..sort((a, b) => a.inicioDia.compareTo(b.inicioDia));
return PluriGlassSurface(
@@ -738,7 +768,7 @@ class _PanelVacaciones extends StatelessWidget {
const SizedBox(width: 10),
Expanded(
child: Text(
'Rangos de vacaciones',
l10n.vacationRangesTitle,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
@@ -747,17 +777,14 @@ class _PanelVacaciones extends StatelessWidget {
FilledButton.tonalIcon(
onPressed: () => _abrirAlta(context),
icon: const Icon(Icons.add_rounded),
label: const Text('Agregar'),
label: Text(l10n.addAction),
),
],
),
const SizedBox(height: 8),
Text(
'Si una alarma tiene "Pausa en vacaciones", se salta automáticamente estos rangos.',
),
const SizedBox(height: 10),
Text(l10n.vacationRangesHint),
if (vacaciones.isEmpty)
const Text('Sin rangos cargados.')
Text(l10n.vacationRangesEmpty)
else
for (final rango in vacaciones)
ListTile(
@@ -768,7 +795,7 @@ class _PanelVacaciones extends StatelessWidget {
'${_fechaCorta(rango.inicioDia)}${_fechaCorta(rango.finDia)}',
),
trailing: IconButton(
tooltip: 'Eliminar rango',
tooltip: l10n.deleteRangeAction,
onPressed: () => estado.eliminarRangoVacaciones(rango.id),
icon: const Icon(Icons.delete_outline_rounded),
),
@@ -807,7 +834,9 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
final hoy = DateTime.now();
_inicio = DateTime(hoy.year, hoy.month, hoy.day);
_fin = _inicio.add(const Duration(days: 2));
_nombreController = TextEditingController(text: 'Vacaciones');
_nombreController = TextEditingController(
text: AppLocalizations.of(context).vacationsDefaultName,
);
}
@override
@@ -818,6 +847,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final bottom = MediaQuery.of(context).viewInsets.bottom;
return Padding(
padding: EdgeInsets.fromLTRB(12, 12, 12, bottom + 12),
@@ -829,7 +859,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nuevo rango de vacaciones',
l10n.newVacationRangeTitle,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
@@ -837,7 +867,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
const SizedBox(height: 12),
TextField(
controller: _nombreController,
decoration: const InputDecoration(labelText: 'Nombre'),
decoration: InputDecoration(labelText: l10n.nameLabel),
),
const SizedBox(height: 12),
Row(
@@ -845,7 +875,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
Expanded(
child: _PickerButton(
icon: Icons.play_arrow_rounded,
label: 'Inicio',
label: l10n.startLabel,
value: _fechaCorta(_inicio),
onTap: () => _elegirFecha(esInicio: true),
),
@@ -854,7 +884,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
Expanded(
child: _PickerButton(
icon: Icons.stop_rounded,
label: 'Fin',
label: l10n.endLabel,
value: _fechaCorta(_fin),
onTap: () => _elegirFecha(esInicio: false),
),
@@ -865,7 +895,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
FilledButton.icon(
onPressed: _guardar,
icon: const Icon(Icons.check_rounded),
label: const Text('Guardar rango'),
label: Text(l10n.saveRangeAction),
),
],
),
@@ -1012,14 +1042,15 @@ class _EmptyAlarmas extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const PluriGlassSurface(
final l10n = AppLocalizations.of(context);
return PluriGlassSurface(
child: Column(
children: [
_AssetIcon('assets/icons/alarmas/alarm_music.png', size: 92),
SizedBox(height: 12),
Text('Todavía no hay alarmas.'),
SizedBox(height: 4),
Text('Creá una para diseñar tu despertar musical.'),
const _AssetIcon('assets/icons/alarmas/alarm_music.png', size: 92),
const SizedBox(height: 12),
Text(l10n.noAlarmsYetTitle),
const SizedBox(height: 4),
Text(l10n.noAlarmsYetSubtitle),
],
),
);
@@ -1029,59 +1060,20 @@ class _EmptyAlarmas extends StatelessWidget {
String _hora(AlarmaMusical alarma) =>
'${alarma.hora.toString().padLeft(2, '0')}:${alarma.minuto.toString().padLeft(2, '0')}';
String _programacion(AlarmaMusical alarma) {
String _programacion(AppLocalizations l10n, AlarmaMusical alarma) {
return switch (alarma.tipoProgramacion) {
TipoProgramacionAlarma.unica =>
'Una vez · ${_fechaCorta(alarma.fechaUnica ?? DateTime.now())}',
TipoProgramacionAlarma.diaria => 'Diaria',
l10n.alarmScheduleOnce(_fechaCorta(alarma.fechaUnica ?? DateTime.now())),
TipoProgramacionAlarma.diaria => l10n.dailyOption,
TipoProgramacionAlarma.diasSemana =>
'Días: ${alarma.diasSemana.map(_diaCorto).join(', ')}',
l10n.alarmScheduleWeekdays(
alarma.diasSemana.map(l10n.weekdayShort).join(', '),
),
};
}
String _fechaHora(DateTime fecha) {
final local = fecha.toLocal();
return '${_diaLargo(local.weekday)} ${local.day} de ${_mes(local.month)} a las '
'${local.hour.toString().padLeft(2, '0')}:${local.minute.toString().padLeft(2, '0')}';
}
String _fechaHora(AppLocalizations l10n, DateTime fecha) =>
l10n.dateTimeSentence(fecha);
String _fechaCorta(DateTime fecha) =>
'${fecha.day.toString().padLeft(2, '0')}/${fecha.month.toString().padLeft(2, '0')}/${fecha.year}';
String _diaCorto(int dia) => switch (dia) {
DateTime.monday => 'Lun',
DateTime.tuesday => 'Mar',
DateTime.wednesday => 'Mié',
DateTime.thursday => 'Jue',
DateTime.friday => 'Vie',
DateTime.saturday => 'Sáb',
DateTime.sunday => 'Dom',
_ => '?',
};
String _diaLargo(int dia) => switch (dia) {
DateTime.monday => 'lunes',
DateTime.tuesday => 'martes',
DateTime.wednesday => 'miércoles',
DateTime.thursday => 'jueves',
DateTime.friday => 'viernes',
DateTime.saturday => 'sábado',
DateTime.sunday => 'domingo',
_ => 'día',
};
String _mes(int mes) => switch (mes) {
1 => 'enero',
2 => 'febrero',
3 => 'marzo',
4 => 'abril',
5 => 'mayo',
6 => 'junio',
7 => 'julio',
8 => 'agosto',
9 => 'septiembre',
10 => 'octubre',
11 => 'noviembre',
12 => 'diciembre',
_ => 'mes',
};