fix(alarmas): show which days a weekday alarm actually fires on
The alarms list showed a generic "Días" label for a diasSemana alarm instead of its actual configured days. Render the real recurrence (e.g. "Lun, Mié, Vie") by reusing the SAME per-day abbreviation the editor's own day-picker circles already use -- no new formatting scheme, no new ARB keys for the days themselves. Also surface fade/volume/vacation-pause state on the card, each only when it is a genuinely useful deviation from the common case: a fade badge when fadeInSegundos > 0 (reusing the existing alarmFadeInLabel key), a volume percentage when it differs from the 85% default, and a vacation-paused badge when the alarm is both configured to pause and a vacation range is currently active (mirrors the exact predicate ServicioProgramacionAlarmas already uses). One compact line, not a badge per field. Fixes a text-collision regression in pantalla_alarmas_editor_test.dart: opening the editor for an alarm whose own day now renders on its card (e.g. "Lun") made a bare find.text(weekday) ambiguous against the editor's day-picker circle with the same label -- scoped that finder to the BottomSheet subtree.
This commit is contained in:
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "متوقفة مؤقتًا بسبب الإجازة"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "ছুটির কারণে বিরত"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Wegen Urlaub pausiert"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Paused for vacation"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -799,5 +799,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Pausada por vacaciones"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "En pause pour les vacances"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "छुट्टी के कारण रोका गया"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Dijeda karena liburan"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "In pausa per le vacanze"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "休暇のため一時停止中"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Pausada por férias"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "Приостановлено на время отпуска"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -840,5 +840,6 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"alarmCardVacationPausedBadge": "因假期已暂停"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3067,6 +3067,12 @@ abstract class AppLocalizations {
|
|||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
/// **'Preset: {preset}'**
|
/// **'Preset: {preset}'**
|
||||||
String eqCustomActionPresetLabel(String preset);
|
String eqCustomActionPresetLabel(String preset);
|
||||||
|
|
||||||
|
/// No description provided for @alarmCardVacationPausedBadge.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Pausada por vacaciones'**
|
||||||
|
String get alarmCardVacationPausedBadge;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -1687,4 +1687,7 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'الإعداد المسبق: $preset';
|
return 'الإعداد المسبق: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'متوقفة مؤقتًا بسبب الإجازة';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1696,4 +1696,7 @@ class AppLocalizationsBn extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'প্রিসেট: $preset';
|
return 'প্রিসেট: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'ছুটির কারণে বিরত';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1709,4 +1709,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Voreinstellung: $preset';
|
return 'Voreinstellung: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Wegen Urlaub pausiert';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1689,4 +1689,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Preset: $preset';
|
return 'Preset: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Paused for vacation';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1703,4 +1703,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Preset: $preset';
|
return 'Preset: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Pausada por vacaciones';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1712,4 +1712,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Préréglage : $preset';
|
return 'Préréglage : $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'En pause pour les vacances';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1691,4 +1691,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'प्रीसेट: $preset';
|
return 'प्रीसेट: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'छुट्टी के कारण रोका गया';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1699,4 +1699,7 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Prasetel: $preset';
|
return 'Prasetel: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Dijeda karena liburan';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1711,4 +1711,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Preset attivo: $preset';
|
return 'Preset attivo: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'In pausa per le vacanze';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1642,4 +1642,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'プリセット: $preset';
|
return 'プリセット: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => '休暇のため一時停止中';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1699,4 +1699,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Predefinição: $preset';
|
return 'Predefinição: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Pausada por férias';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1703,4 +1703,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return 'Пресет: $preset';
|
return 'Пресет: $preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => 'Приостановлено на время отпуска';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1634,4 +1634,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String eqCustomActionPresetLabel(String preset) {
|
String eqCustomActionPresetLabel(String preset) {
|
||||||
return '预设:$preset';
|
return '预设:$preset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alarmCardVacationPausedBadge => '因假期已暂停';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,6 +265,26 @@ class _TarjetaAlarma extends StatelessWidget {
|
|||||||
? l10n.noStationUseInternalSound
|
? l10n.noStationUseInternalSound
|
||||||
: localizedStationName(l10n, alarma.emisora!.nombre);
|
: localizedStationName(l10n, alarma.emisora!.nombre);
|
||||||
|
|
||||||
|
// Item 5: surfaces the genuinely useful fields that already exist on
|
||||||
|
// the model, WITHOUT turning the row into clutter -- each is shown
|
||||||
|
// only when it is a meaningful deviation from the common case.
|
||||||
|
// Mirrors EXACTLY the pause predicate `impactoDeRango`/
|
||||||
|
// `ServicioProgramacionAlarmas` already use
|
||||||
|
// (`!sonarEnVacaciones` while `activa`), gated by whether a vacation
|
||||||
|
// range is CURRENTLY active -- an alarm configured to pause but with
|
||||||
|
// no active range right now is not actually paused by anything yet.
|
||||||
|
final pausadaPorVacaciones =
|
||||||
|
alarma.activa &&
|
||||||
|
!alarma.sonarEnVacaciones &&
|
||||||
|
estado.rangoVacacionesActivo() != null;
|
||||||
|
final detalles = <String>[
|
||||||
|
if (alarma.fadeInSegundos > 0)
|
||||||
|
l10n.alarmFadeInLabel(alarma.fadeInSegundos),
|
||||||
|
if ((alarma.volumen * 100).round() != 85)
|
||||||
|
'${(alarma.volumen * 100).round()}%',
|
||||||
|
if (pausadaPorVacaciones) l10n.alarmCardVacationPausedBadge,
|
||||||
|
];
|
||||||
|
|
||||||
return Dismissible(
|
return Dismissible(
|
||||||
key: ValueKey('tarjeta-alarma-${alarma.id}'),
|
key: ValueKey('tarjeta-alarma-${alarma.id}'),
|
||||||
direction: DismissDirection.horizontal,
|
direction: DismissDirection.horizontal,
|
||||||
@@ -309,14 +329,21 @@ class _TarjetaAlarma extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
// Item 5: real day list can run longer than the
|
||||||
|
// old generic "Días" label -- Flexible+ellipsis
|
||||||
|
// keeps a long selection from overflowing the
|
||||||
|
// Row instead of clipping visibly.
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
_recurrenciaCorta(l10n, alarma),
|
_recurrenciaCorta(l10n, alarma),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: Theme.of(
|
color: Theme.of(context).colorScheme
|
||||||
context,
|
.onSurface
|
||||||
).colorScheme.onSurface.withValues(alpha: 0.5),
|
.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -369,6 +396,25 @@ class _TarjetaAlarma extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// Item 5: fade/volume/vacation-pause state, only
|
||||||
|
// when each is a genuinely useful deviation from
|
||||||
|
// the common case (see `detalles` above) -- a
|
||||||
|
// single compact line, not a badge per field.
|
||||||
|
if (detalles.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
detalles.join(' · '),
|
||||||
|
key: ValueKey('tarjeta-alarma-detalles-${alarma.id}'),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface.withValues(alpha: 0.55),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1668,16 +1714,39 @@ String _weekdayShort(AppLocalizations l10n, int day) => switch (day) {
|
|||||||
String _fechaCorta(AppLocalizations l10n, DateTime fecha) =>
|
String _fechaCorta(AppLocalizations l10n, DateTime fecha) =>
|
||||||
fechaCortaLocalizada(l10n.localeName, fecha);
|
fechaCortaLocalizada(l10n.localeName, fecha);
|
||||||
|
|
||||||
/// Audit 7.4 (t4:339): a compact recurrence label next to the alarm card's
|
/// Audit 7.4 (t4:339) / item 5: a compact recurrence label next to the
|
||||||
/// giant time. Reuses the SAME generic labels the editor's own
|
/// alarm card's giant time. `diaria`/`unica` still show the SAME generic
|
||||||
/// `TipoProgramacionAlarma` `SegmentedButton` already shows (`oneTimeOption`
|
/// labels the editor's own `TipoProgramacionAlarma` `SegmentedButton`
|
||||||
/// / `dailyOption` / `weekdaysOption`) rather than inventing a new, more
|
/// already uses (`dailyOption`/`oneTimeOption`) -- both are already fully
|
||||||
/// specific ARB string -- honest given the space (12px, next to a 34px
|
/// specific (there is nothing more concrete to say than "every day"/"just
|
||||||
/// time) genuinely only fits a short word, not a full weekday list.
|
/// once"). `diasSemana` now renders the alarm's ACTUAL configured days
|
||||||
|
/// (e.g. "Lun, Mié, Vie") instead of the generic `weekdaysOption` ("Días"),
|
||||||
|
/// reusing [_weekdayShort] (the SAME per-day abbreviation the editor's own
|
||||||
|
/// day-picker circles already use) -- no new ARB keys, no second
|
||||||
|
/// formatting scheme, and the resulting Text is wrapped in a
|
||||||
|
/// `Flexible`+ellipsis at the call site so a long selection never
|
||||||
|
/// overflows the row.
|
||||||
String _recurrenciaCorta(AppLocalizations l10n, AlarmaMusical alarma) {
|
String _recurrenciaCorta(AppLocalizations l10n, AlarmaMusical alarma) {
|
||||||
return switch (alarma.tipoProgramacion) {
|
return switch (alarma.tipoProgramacion) {
|
||||||
TipoProgramacionAlarma.diaria => l10n.dailyOption,
|
TipoProgramacionAlarma.diaria => l10n.dailyOption,
|
||||||
TipoProgramacionAlarma.diasSemana => l10n.weekdaysOption,
|
TipoProgramacionAlarma.diasSemana => _diasSemanaCorto(
|
||||||
|
l10n,
|
||||||
|
alarma.diasSemana,
|
||||||
|
),
|
||||||
TipoProgramacionAlarma.unica => l10n.oneTimeOption,
|
TipoProgramacionAlarma.unica => l10n.oneTimeOption,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The real, ordered day abbreviations for a `diasSemana` alarm (item 5),
|
||||||
|
/// e.g. "Lun, Mié, Vie". [diasSemana] is re-sorted defensively (the editor
|
||||||
|
/// always persists it sorted, but this does not rely on that). Falls back
|
||||||
|
/// to the generic [AppLocalizations.weekdaysOption] label when
|
||||||
|
/// [diasSemana] is empty -- the editor already blocks saving an empty
|
||||||
|
/// selection in this mode, but a corrupt/legacy persisted record could
|
||||||
|
/// still reach here, and showing nothing would be worse than the old
|
||||||
|
/// generic label.
|
||||||
|
String _diasSemanaCorto(AppLocalizations l10n, List<int> diasSemana) {
|
||||||
|
if (diasSemana.isEmpty) return l10n.weekdaysOption;
|
||||||
|
final ordenados = [...diasSemana]..sort();
|
||||||
|
return ordenados.map((dia) => _weekdayShort(l10n, dia)).join(', ');
|
||||||
|
}
|
||||||
|
|||||||
@@ -190,9 +190,26 @@ void main() {
|
|||||||
expect(antes, isNot(l10n.alarmNoNextExecution));
|
expect(antes, isNot(l10n.alarmNoNextExecution));
|
||||||
|
|
||||||
// Lunes -> Martes: la fecha calculada SIEMPRE cambia, sea cual sea hoy.
|
// Lunes -> Martes: la fecha calculada SIEMPRE cambia, sea cual sea hoy.
|
||||||
await tester.tap(find.text(l10n.weekdayShortTuesday));
|
//
|
||||||
|
// Item 5: the alarm CARD underneath now also renders the real day
|
||||||
|
// abbreviation ("Lun") for a diasSemana alarm, so a bare
|
||||||
|
// `find.text(...)` for a weekday letter is ambiguous while the
|
||||||
|
// editor sheet is open on top of the list — scope to the sheet's own
|
||||||
|
// BottomSheet subtree to target the day-picker circle specifically.
|
||||||
|
final hojaEditor = find.byType(BottomSheet);
|
||||||
|
await tester.tap(
|
||||||
|
find.descendant(
|
||||||
|
of: hojaEditor,
|
||||||
|
matching: find.text(l10n.weekdayShortTuesday),
|
||||||
|
),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.tap(find.text(l10n.weekdayShortMonday));
|
await tester.tap(
|
||||||
|
find.descendant(
|
||||||
|
of: hojaEditor,
|
||||||
|
matching: find.text(l10n.weekdayShortMonday),
|
||||||
|
),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final despues = _textoPreview(tester);
|
final despues = _textoPreview(tester);
|
||||||
|
|||||||
@@ -0,0 +1,330 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pluriwave/estado/estado_alarmas.dart';
|
||||||
|
import 'package:pluriwave/estado/estado_radio.dart';
|
||||||
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
||||||
|
import 'package:pluriwave/modelos/alarma_musical.dart';
|
||||||
|
import 'package:pluriwave/pantallas/pantalla_alarmas.dart';
|
||||||
|
import 'package:pluriwave/servicios/servicio_alarmas.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../helpers/fakes.dart';
|
||||||
|
import '../helpers/fakes_alarmas.dart';
|
||||||
|
|
||||||
|
/// Item 5: the alarm list must show which days a `diasSemana` alarm
|
||||||
|
/// actually fires on (e.g. "Lun, Mié, Vie"), not the generic "Días" label,
|
||||||
|
/// plus surface fade/volume/vacation-pause state when they are genuinely
|
||||||
|
/// informative -- without cluttering the row.
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<(EstadoRadio, EstadoAlarmas)> montar(
|
||||||
|
WidgetTester tester, {
|
||||||
|
required AlarmaMusical alarma,
|
||||||
|
List<RangoVacaciones> vacaciones = const [],
|
||||||
|
}) async {
|
||||||
|
tester.view.physicalSize = const Size(1440, 3200);
|
||||||
|
tester.view.devicePixelRatio = 1.0;
|
||||||
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
|
final radio = EstadoRadio(
|
||||||
|
audio: FakeServicioAudio(),
|
||||||
|
favoritos: FakeServicioFavoritos(),
|
||||||
|
radio: FakeServicioRadio(),
|
||||||
|
servicioEcualizador: FakeServicioEcualizador(),
|
||||||
|
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
|
||||||
|
iniciarAutomaticamente: false,
|
||||||
|
);
|
||||||
|
addTearDown(radio.dispose);
|
||||||
|
|
||||||
|
final android = FakePuertoAlarmasAndroid();
|
||||||
|
final estadoAlarmas = EstadoAlarmas(
|
||||||
|
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 11, 6, 0)),
|
||||||
|
android: android,
|
||||||
|
iniciarAutomaticamente: false,
|
||||||
|
);
|
||||||
|
addTearDown(estadoAlarmas.dispose);
|
||||||
|
addTearDown(android.dispose);
|
||||||
|
|
||||||
|
await estadoAlarmas.guardarAlarma(alarma);
|
||||||
|
if (vacaciones.isNotEmpty) {
|
||||||
|
await estadoAlarmas.guardarVacaciones(vacaciones);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider<EstadoRadio>.value(value: radio),
|
||||||
|
ChangeNotifierProvider<EstadoAlarmas>.value(value: estadoAlarmas),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
locale: const Locale('es'),
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const Scaffold(body: PantallaAlarmas()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
return (radio, estadoAlarmas);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'diasSemana alarm shows the ACTUAL configured days (Lun, Mié, Vie), '
|
||||||
|
'not the generic "Días" label',
|
||||||
|
(tester) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-dias',
|
||||||
|
nombre: 'Entre semana',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
|
||||||
|
diasSemana: [DateTime.monday, DateTime.wednesday, DateTime.friday],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('Lun, Mié, Vie'), findsOneWidget);
|
||||||
|
expect(find.text('Días'), findsNothing);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('daily alarm still shows "Diaria" (unaffected)', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-diaria',
|
||||||
|
nombre: 'Todos los días',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('Diaria'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('one-time alarm still shows "Una vez" (unaffected)', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-unica',
|
||||||
|
nombre: 'Una sola vez',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.unica,
|
||||||
|
diasSemana: [],
|
||||||
|
fechaUnica: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('Una vez'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'a diasSemana alarm with an (invalid/legacy) empty diasSemana falls '
|
||||||
|
'back to the generic label instead of showing nothing',
|
||||||
|
(tester) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-dias-vacio',
|
||||||
|
nombre: 'Corrupta',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
|
||||||
|
diasSemana: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('Días'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('a configured fade-in shows a compact "Fade-in Ns" detail', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-fade',
|
||||||
|
nombre: 'Con fade',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
fadeInSegundos: 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.textContaining('Fade-in 8s'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('no fade-in (0s, the default) shows no fade detail', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-sin-fade',
|
||||||
|
nombre: 'Sin fade',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
fadeInSegundos: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.textContaining('Fade-in'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'a non-default volume shows a compact percentage detail',
|
||||||
|
(tester) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-vol',
|
||||||
|
nombre: 'Volumen bajo',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
volumen: 0.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.textContaining('50%'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('the default volume (85%) shows no volume detail', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-vol-default',
|
||||||
|
nombre: 'Volumen default',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.textContaining('85%'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'an alarm paused by a CURRENTLY active vacation range shows a '
|
||||||
|
'vacation-paused detail',
|
||||||
|
(tester) async {
|
||||||
|
final l10n = lookupAppLocalizations(const Locale('es'));
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-vacaciones',
|
||||||
|
nombre: 'Pausada',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
sonarEnVacaciones: false,
|
||||||
|
),
|
||||||
|
vacaciones: [
|
||||||
|
// Wide, real-wall-clock-safe range (rangoVacacionesActivo()
|
||||||
|
// defaults to the REAL DateTime.now(), not this file's injected
|
||||||
|
// `reloj`) -- deliberately spans many years so the test stays
|
||||||
|
// valid regardless of exactly when it runs.
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'v1',
|
||||||
|
nombre: 'Verano',
|
||||||
|
inicio: DateTime(2020, 1, 1),
|
||||||
|
fin: DateTime(2030, 12, 31),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.textContaining(l10n.alarmCardVacationPausedBadge),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'an alarm that DOES sound during vacations shows NO vacation-paused '
|
||||||
|
'detail even with an active range',
|
||||||
|
(tester) async {
|
||||||
|
final l10n = lookupAppLocalizations(const Locale('es'));
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-suena-vacaciones',
|
||||||
|
nombre: 'Suena igual',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
sonarEnVacaciones: true,
|
||||||
|
),
|
||||||
|
vacaciones: [
|
||||||
|
// Wide, real-wall-clock-safe range (rangoVacacionesActivo()
|
||||||
|
// defaults to the REAL DateTime.now(), not this file's injected
|
||||||
|
// `reloj`) -- deliberately spans many years so the test stays
|
||||||
|
// valid regardless of exactly when it runs.
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'v1',
|
||||||
|
nombre: 'Verano',
|
||||||
|
inicio: DateTime(2020, 1, 1),
|
||||||
|
fin: DateTime(2030, 12, 31),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.textContaining(l10n.alarmCardVacationPausedBadge),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'sonarEnVacaciones:false with NO currently-active vacation range shows '
|
||||||
|
'no vacation-paused detail (nothing to be paused BY right now)',
|
||||||
|
(tester) async {
|
||||||
|
final l10n = lookupAppLocalizations(const Locale('es'));
|
||||||
|
await montar(
|
||||||
|
tester,
|
||||||
|
alarma: const AlarmaMusical(
|
||||||
|
id: 'a-sin-rango-activo',
|
||||||
|
nombre: 'Sin vacaciones activas',
|
||||||
|
hora: 7,
|
||||||
|
minuto: 0,
|
||||||
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||||
|
diasSemana: [],
|
||||||
|
sonarEnVacaciones: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.textContaining(l10n.alarmCardVacationPausedBadge),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user