fix(alarmas-editor): REPETIR eyebrow, grouped card, volume row
Audit 8.5 (t4:381): the "REPETIR" eyebrow above the weekday circles -- never rendered before. New ARB key alarmRepeatSectionLabel. Audit 8.6 (t4:392-401): the station picker, volume, fade-in and vacation toggle now share ONE bordered card with sangred divider lines -- were four separately-spaced widgets. Restyled _CampoSelectorEmisora as a flat nav row (icon/bold label/muted value/chevron, matching FilaAjuste's own convention) so it fits inside the card instead of drawing its own outlined InputDecoration chrome -- used by BOTH the primary and fallback (Advanced) station fields. Snooze duration, "use current station", and the Advanced section stay OUTSIDE the card, exactly where they were (audit 8.8's own documented deliberate extras). A transparent Material sits inside the card's coloured box so the fade-in ListTile and the vacation SwitchListTile still paint their ink/background correctly (the same class of constraint already documented for PluriGlassSurface elsewhere in this codebase). Audit 8.7 (t4:396): the volume row is now a compact 112px track with a live percentage label -- was a bare full-width Slider with no visible value. New ArtB key alarmVolumeLabel. Item 8.8 (extra name/type-selector/snooze-selector/"use current station"/Advanced fields) stays exactly as documented: deliberate, required by the hard constraint that the editor keep its date field, fallback-station picker and sound dropdown.
This commit is contained in:
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "تكرار",
|
||||
"alarmVolumeLabel": "مستوى الصوت",
|
||||
"androidReliabilityTitle": "مراجعة موثوقية Android",
|
||||
"closeAction": "إغلاق",
|
||||
"customOption": "مخصص",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "পুনরাবৃত্তি",
|
||||
"alarmVolumeLabel": "ভলিউম",
|
||||
"androidReliabilityTitle": "Android নির্ভরযোগ্যতা দেখুন",
|
||||
"closeAction": "বন্ধ করুন",
|
||||
"customOption": "কাস্টম",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "WIEDERHOLEN",
|
||||
"alarmVolumeLabel": "Lautstärke",
|
||||
"androidReliabilityTitle": "Android-Zuverlässigkeit prüfen",
|
||||
"closeAction": "Schließen",
|
||||
"customOption": "Benutzerdefiniert",
|
||||
|
||||
@@ -705,6 +705,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "REPEAT",
|
||||
"alarmVolumeLabel": "Volume",
|
||||
"androidReliabilityTitle": "Review Android reliability",
|
||||
"closeAction": "Close",
|
||||
"customOption": "Custom",
|
||||
|
||||
@@ -661,6 +661,7 @@
|
||||
"alarmScheduleWeekdays": "Días: {days}",
|
||||
"@alarmScheduleWeekdays": {"placeholders": {"days": {}}},
|
||||
"alarmRepeatSectionLabel": "REPETIR",
|
||||
"alarmVolumeLabel": "Volumen",
|
||||
"androidReliabilityTitle": "Revisar fiabilidad Android",
|
||||
"closeAction": "Cerrar",
|
||||
"customOption": "Personalizada",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "RÉPÉTER",
|
||||
"alarmVolumeLabel": "Volume",
|
||||
"androidReliabilityTitle": "Vérifier la fiabilité Android",
|
||||
"closeAction": "Fermer",
|
||||
"customOption": "Personnalisée",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "दोहराएं",
|
||||
"alarmVolumeLabel": "आवाज़",
|
||||
"androidReliabilityTitle": "Android विश्वसनीयता जाँचें",
|
||||
"closeAction": "बंद करें",
|
||||
"customOption": "कस्टम",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "ULANGI",
|
||||
"alarmVolumeLabel": "Volume",
|
||||
"androidReliabilityTitle": "Tinjau keandalan Android",
|
||||
"closeAction": "Tutup",
|
||||
"customOption": "Kustom",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "RIPETI",
|
||||
"alarmVolumeLabel": "Volume",
|
||||
"androidReliabilityTitle": "Controlla affidabilità Android",
|
||||
"closeAction": "Chiudi",
|
||||
"customOption": "Personalizzata",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "繰り返し",
|
||||
"alarmVolumeLabel": "音量",
|
||||
"androidReliabilityTitle": "Androidの信頼性を確認",
|
||||
"closeAction": "閉じる",
|
||||
"customOption": "カスタム",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "REPETIÇÃO",
|
||||
"alarmVolumeLabel": "Volume",
|
||||
"androidReliabilityTitle": "Revisar confiabilidade Android",
|
||||
"closeAction": "Fechar",
|
||||
"customOption": "Personalizada",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "ПОВТОР",
|
||||
"alarmVolumeLabel": "Громкость",
|
||||
"androidReliabilityTitle": "Проверить надёжность Android",
|
||||
"closeAction": "Закрыть",
|
||||
"customOption": "Своя",
|
||||
|
||||
@@ -594,6 +594,7 @@
|
||||
}
|
||||
},
|
||||
"alarmRepeatSectionLabel": "重复",
|
||||
"alarmVolumeLabel": "音量",
|
||||
"androidReliabilityTitle": "检查 Android 可靠性",
|
||||
"closeAction": "关闭",
|
||||
"customOption": "自定义",
|
||||
|
||||
@@ -2498,6 +2498,12 @@ abstract class AppLocalizations {
|
||||
/// **'REPETIR'**
|
||||
String get alarmRepeatSectionLabel;
|
||||
|
||||
/// No description provided for @alarmVolumeLabel.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Volumen'**
|
||||
String get alarmVolumeLabel;
|
||||
|
||||
/// No description provided for @androidReliabilityTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
|
||||
@@ -1372,6 +1372,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'تكرار';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'مستوى الصوت';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'مراجعة موثوقية Android';
|
||||
|
||||
|
||||
@@ -1381,6 +1381,9 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'পুনরাবৃত্তি';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'ভলিউম';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Android নির্ভরযোগ্যতা দেখুন';
|
||||
|
||||
|
||||
@@ -1389,6 +1389,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'WIEDERHOLEN';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Lautstärke';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Android-Zuverlässigkeit prüfen';
|
||||
|
||||
|
||||
@@ -1373,6 +1373,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'REPEAT';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volume';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Review Android reliability';
|
||||
|
||||
|
||||
@@ -1383,6 +1383,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'REPETIR';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volumen';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Revisar fiabilidad Android';
|
||||
|
||||
|
||||
@@ -1393,6 +1393,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'RÉPÉTER';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volume';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Vérifier la fiabilité Android';
|
||||
|
||||
|
||||
@@ -1376,6 +1376,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'दोहराएं';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'आवाज़';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Android विश्वसनीयता जाँचें';
|
||||
|
||||
|
||||
@@ -1384,6 +1384,9 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'ULANGI';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volume';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Tinjau keandalan Android';
|
||||
|
||||
|
||||
@@ -1392,6 +1392,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'RIPETI';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volume';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Controlla affidabilità Android';
|
||||
|
||||
|
||||
@@ -1333,6 +1333,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => '繰り返し';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => '音量';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Androidの信頼性を確認';
|
||||
|
||||
|
||||
@@ -1381,6 +1381,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'REPETIÇÃO';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Volume';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Revisar confiabilidade Android';
|
||||
|
||||
|
||||
@@ -1385,6 +1385,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => 'ПОВТОР';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => 'Громкость';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => 'Проверить надёжность Android';
|
||||
|
||||
|
||||
@@ -1328,6 +1328,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get alarmRepeatSectionLabel => '重复';
|
||||
|
||||
@override
|
||||
String get alarmVolumeLabel => '音量';
|
||||
|
||||
@override
|
||||
String get androidReliabilityTitle => '检查 Android 可靠性';
|
||||
|
||||
|
||||
+246
-113
@@ -647,6 +647,15 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
(value) => setState(() => _tipo = value.first),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Audit 8.5 (t4 line 381): the "REPETIR" eyebrow above the
|
||||
// weekday circles -- never rendered anywhere before.
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 8),
|
||||
child: Text(
|
||||
l10n.alarmRepeatSectionLabel,
|
||||
style: context.pluriType.eyebrowLabel,
|
||||
),
|
||||
),
|
||||
// WU10: weekday circles are now ALWAYS visible (previously
|
||||
// only inserted into the tree in diasSemana mode) — matching
|
||||
// the mockup, which shows them unconditionally under the
|
||||
@@ -688,80 +697,117 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
const SizedBox(height: 12),
|
||||
_vistaProximaEjecucion(l10n),
|
||||
const SizedBox(height: 14),
|
||||
_SectionLabel(
|
||||
icon: 'assets/icons/alarmas/fallback_sound.png',
|
||||
text: l10n.soundAndVolumeSection,
|
||||
),
|
||||
Slider(
|
||||
value: _volumen,
|
||||
// S2-R11: floor lowered from 0.25 to 0.0.
|
||||
min: 0,
|
||||
max: 1,
|
||||
divisions: 20,
|
||||
label: '${(_volumen * 100).round()}%',
|
||||
onChanged: (value) => setState(() => _volumen = value),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
l10n.alarmFadeInTitle,
|
||||
style: context.pluriType.cardTitle,
|
||||
// Audit 8.6 (t4 lines 392-401): the station picker, volume,
|
||||
// fade-in and vacation toggle now share ONE bordered card
|
||||
// with sangred divider lines between rows -- matching the
|
||||
// prototype's own single grouped block -- instead of
|
||||
// sitting bare on the sheet, each with its own gaps. The
|
||||
// extra fields the prototype does NOT show (name, type
|
||||
// selector, snooze selector, "use current station",
|
||||
// Advanced) stay OUTSIDE this card, exactly where they
|
||||
// were (documented as deliberate in audit 8.8 / WU10).
|
||||
DecoratedBox(
|
||||
key: const ValueKey('alarm-editor-grouped-card'),
|
||||
decoration: BoxDecoration(
|
||||
color: context.pluriTokens.listSurface,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.08),
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
_fadeInSegundos == 0
|
||||
? l10n.alarmFadeInOff
|
||||
: l10n.alarmFadeInSummary(_fadeInSegundos),
|
||||
// A transparent Material sits directly inside the coloured
|
||||
// box, closer to the fade-in ListTile and the vacation
|
||||
// SwitchListTile than the DecoratedBox's own opaque fill
|
||||
// -- both paint their ink/background on the NEAREST
|
||||
// Material ancestor, and without this the box's colour
|
||||
// would hide those effects (Flutter's own debug-mode
|
||||
// check for exactly this).
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
children: [
|
||||
// S2-R9: searchable bottom-sheet picker instead of a
|
||||
// dropdown, for the primary station. The backup
|
||||
// (fallback) picker moves into the Advanced section
|
||||
// below (WU10) — the primary choice stays a
|
||||
// top-level field, only its secondary/backup sibling
|
||||
// is now one tap further away.
|
||||
_CampoSelectorEmisora(
|
||||
key: const ValueKey('alarm-station-field'),
|
||||
label: l10n.favoriteStationLabel,
|
||||
icon: Icons.radio_rounded,
|
||||
value:
|
||||
_emisora == null
|
||||
? l10n.noStationUseInternalSound
|
||||
: localizedStationName(
|
||||
l10n,
|
||||
_emisora!.nombre,
|
||||
),
|
||||
onTap:
|
||||
() => _elegirEmisora(
|
||||
favoritas,
|
||||
seleccionar:
|
||||
(emisora) =>
|
||||
setState(() => _emisora = emisora),
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, indent: 66),
|
||||
// Audit 8.7 (t4 line 396): a compact 112px track with
|
||||
// a trailing percentage label -- was a bare
|
||||
// full-width Slider with no visible value.
|
||||
_FilaVolumen(
|
||||
volumen: _volumen,
|
||||
onChanged:
|
||||
(value) => setState(() => _volumen = value),
|
||||
),
|
||||
const Divider(height: 1, indent: 46),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 4, 14, 4),
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
l10n.alarmFadeInTitle,
|
||||
style: context.pluriType.cardTitle,
|
||||
),
|
||||
subtitle: Text(
|
||||
_fadeInSegundos == 0
|
||||
? l10n.alarmFadeInOff
|
||||
: l10n.alarmFadeInSummary(_fadeInSegundos),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Slider(
|
||||
value: _fadeInSegundos.toDouble(),
|
||||
min: 0,
|
||||
max: 60,
|
||||
divisions: 60,
|
||||
label: '${_fadeInSegundos}s',
|
||||
onChanged:
|
||||
(value) => setState(
|
||||
() => _fadeInSegundos = value.round(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, indent: 46),
|
||||
SwitchListTile.adaptive(
|
||||
value: _sonarEnVacaciones,
|
||||
onChanged:
|
||||
(value) =>
|
||||
setState(() => _sonarEnVacaciones = value),
|
||||
secondary: _AssetIcon(
|
||||
'assets/icons/alarmas/vacation_wave.png',
|
||||
size: 42,
|
||||
semanticLabel: l10n.vacationIconLabel,
|
||||
),
|
||||
title: Text(l10n.playDuringVacations),
|
||||
subtitle: Text(l10n.playDuringVacationsHint),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: _fadeInSegundos.toDouble(),
|
||||
min: 0,
|
||||
max: 60,
|
||||
divisions: 60,
|
||||
label: '${_fadeInSegundos}s',
|
||||
onChanged:
|
||||
(value) =>
|
||||
setState(() => _fadeInSegundos = value.round()),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(l10n.alarmSnoozeDurationTitle),
|
||||
subtitle: Text(l10n.alarmSnoozeOptionLabel(_snoozeMinutos)),
|
||||
),
|
||||
SegmentedButton<int>(
|
||||
segments: [
|
||||
for (final minutos in _opcionesSnooze())
|
||||
ButtonSegment(
|
||||
value: minutos,
|
||||
label: Text(l10n.alarmSnoozeOptionLabel(minutos)),
|
||||
),
|
||||
],
|
||||
selected: {_snoozeMinutos},
|
||||
onSelectionChanged:
|
||||
(value) => setState(() => _snoozeMinutos = value.first),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// S2-R9: searchable bottom-sheet picker instead of a dropdown,
|
||||
// for the primary station. The backup (fallback) picker moves
|
||||
// into the Advanced section below (WU10) — the primary choice
|
||||
// stays a top-level field, only its secondary/backup sibling
|
||||
// is now one tap further away.
|
||||
_CampoSelectorEmisora(
|
||||
key: const ValueKey('alarm-station-field'),
|
||||
label: l10n.favoriteStationLabel,
|
||||
icon: Icons.radio_rounded,
|
||||
value:
|
||||
_emisora == null
|
||||
? l10n.noStationUseInternalSound
|
||||
: localizedStationName(l10n, _emisora!.nombre),
|
||||
onTap:
|
||||
() => _elegirEmisora(
|
||||
favoritas,
|
||||
seleccionar:
|
||||
(emisora) => setState(() => _emisora = emisora),
|
||||
),
|
||||
),
|
||||
if (favoritas.isEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(l10n.saveFavoritesAlarmHint),
|
||||
@@ -778,19 +824,23 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile.adaptive(
|
||||
const SizedBox(height: 12),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: _sonarEnVacaciones,
|
||||
onChanged:
|
||||
(value) => setState(() => _sonarEnVacaciones = value),
|
||||
secondary: _AssetIcon(
|
||||
'assets/icons/alarmas/vacation_wave.png',
|
||||
size: 42,
|
||||
semanticLabel: l10n.vacationIconLabel,
|
||||
),
|
||||
title: Text(l10n.playDuringVacations),
|
||||
subtitle: Text(l10n.playDuringVacationsHint),
|
||||
title: Text(l10n.alarmSnoozeDurationTitle),
|
||||
subtitle: Text(l10n.alarmSnoozeOptionLabel(_snoozeMinutos)),
|
||||
),
|
||||
SegmentedButton<int>(
|
||||
segments: [
|
||||
for (final minutos in _opcionesSnooze())
|
||||
ButtonSegment(
|
||||
value: minutos,
|
||||
label: Text(l10n.alarmSnoozeOptionLabel(minutos)),
|
||||
),
|
||||
],
|
||||
selected: {_snoozeMinutos},
|
||||
onSelectionChanged:
|
||||
(value) => setState(() => _snoozeMinutos = value.first),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// WU10 (native-alarms delta — Alarm Editor Preserves Date,
|
||||
@@ -1033,18 +1083,122 @@ class _CampoSelectorEmisora extends StatelessWidget {
|
||||
final String value;
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// Audit 8.6 (t4:394): a flat nav row -- icon, bold label, muted value,
|
||||
/// chevron -- matching the same convention `FilaAjuste` uses everywhere
|
||||
/// else in Settings, so this fits cleanly inside the grouped card below
|
||||
/// instead of drawing its own outlined `InputDecoration` chrome.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: onTap,
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
prefixIcon: Icon(icon),
|
||||
suffixIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||
final type = context.pluriType;
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: type.cardTitle),
|
||||
Text(
|
||||
value,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: type.bodyStrong.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.55),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
size: 19,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Text(value, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Audit 8.7 (t4 line 396): a compact volume row -- icon, label, a 112px
|
||||
/// track, and a trailing "80%" -- was a bare full-width `Slider` with no
|
||||
/// visible current value. Constrains a real, still-draggable `Slider`
|
||||
/// (not a static bar) to the prototype's 112px track width via a
|
||||
/// `SliderTheme` + fixed-width `SizedBox`, preserving drag interactivity.
|
||||
class _FilaVolumen extends StatelessWidget {
|
||||
const _FilaVolumen({required this.volumen, required this.onChanged});
|
||||
|
||||
final double volumen;
|
||||
final ValueChanged<double> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final onSurface = Theme.of(context).colorScheme.onSurface;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.volume_up_rounded,
|
||||
size: 20,
|
||||
color: onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.alarmVolumeLabel,
|
||||
style: context.pluriType.cardTitle,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 112,
|
||||
height: 24,
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
trackHeight: 4,
|
||||
activeTrackColor: PluriWaveTokens.brand,
|
||||
inactiveTrackColor: Colors.white.withValues(alpha: 0.14),
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 7),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14),
|
||||
),
|
||||
child: Slider(
|
||||
value: volumen,
|
||||
// S2-R11: floor lowered from 0.25 to 0.0.
|
||||
min: 0,
|
||||
max: 1,
|
||||
divisions: 20,
|
||||
label: '${(volumen * 100).round()}%',
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
child: Text(
|
||||
'${(volumen * 100).round()}%',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1446,27 +1600,6 @@ class _PickerButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionLabel extends StatelessWidget {
|
||||
const _SectionLabel({required this.icon, required this.text});
|
||||
|
||||
final String icon;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
_AssetIcon(icon, size: 34),
|
||||
const SizedBox(width: 8),
|
||||
// WU10: swapped the raw TextTheme lookup for the named type-scale
|
||||
// token (cosmetic only — same weight class, now shared with every
|
||||
// other card/section title in the redesign).
|
||||
Text(text, style: context.pluriType.cardTitle),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoticeLine extends StatelessWidget {
|
||||
const _NoticeLine({super.key, required this.icon, required this.text});
|
||||
|
||||
|
||||
@@ -400,6 +400,66 @@ void main() {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('8.5: the "REPETIR" eyebrow sits above the weekday circles', (
|
||||
tester,
|
||||
) async {
|
||||
await _abrirEditor(tester);
|
||||
|
||||
expect(find.text(l10n.alarmRepeatSectionLabel), findsOneWidget);
|
||||
final eyebrowY =
|
||||
tester.getBottomLeft(find.text(l10n.alarmRepeatSectionLabel)).dy;
|
||||
final circlesY =
|
||||
tester
|
||||
.getTopLeft(find.byKey(const ValueKey('alarm-weekday-circles')))
|
||||
.dy;
|
||||
expect(eyebrowY <= circlesY, isTrue);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'8.6: the station field, volume, fade-in and vacation toggle share '
|
||||
'ONE grouped card',
|
||||
(tester) async {
|
||||
await _abrirEditor(tester);
|
||||
|
||||
final tarjeta = find.byKey(const ValueKey('alarm-editor-grouped-card'));
|
||||
expect(tarjeta, findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: tarjeta,
|
||||
matching: find.byKey(const ValueKey('alarm-station-field')),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: tarjeta, matching: find.byType(Slider)),
|
||||
findsNWidgets(2),
|
||||
reason: 'volume + fade-in sliders both live inside the card',
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: tarjeta, matching: find.byType(SwitchListTile)),
|
||||
findsOneWidget,
|
||||
reason: 'the vacation toggle lives inside the card',
|
||||
);
|
||||
// Snooze duration is a deliberate EXTRA (audit 8.8), kept OUTSIDE
|
||||
// the prototype-mandated 4-row card.
|
||||
expect(
|
||||
find.descendant(
|
||||
of: tarjeta,
|
||||
matching: find.text(l10n.alarmSnoozeDurationTitle),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('8.7: the volume row shows a live percentage label next to a '
|
||||
'compact track', (tester) async {
|
||||
await _abrirEditor(tester);
|
||||
|
||||
expect(find.text(l10n.alarmVolumeLabel), findsOneWidget);
|
||||
expect(find.text('85%'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('WU8 — tarjeta de alarma simplificada', () {
|
||||
|
||||
Reference in New Issue
Block a user