diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 2c53529..62677f7 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "تكرار", + "alarmVolumeLabel": "مستوى الصوت", "androidReliabilityTitle": "مراجعة موثوقية Android", "closeAction": "إغلاق", "customOption": "مخصص", diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 68545c2..f545b7f 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "পুনরাবৃত্তি", + "alarmVolumeLabel": "ভলিউম", "androidReliabilityTitle": "Android নির্ভরযোগ্যতা দেখুন", "closeAction": "বন্ধ করুন", "customOption": "কাস্টম", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 96a177f..5683aa7 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "WIEDERHOLEN", + "alarmVolumeLabel": "Lautstärke", "androidReliabilityTitle": "Android-Zuverlässigkeit prüfen", "closeAction": "Schließen", "customOption": "Benutzerdefiniert", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3f11097..cc04eb8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -705,6 +705,7 @@ } }, "alarmRepeatSectionLabel": "REPEAT", + "alarmVolumeLabel": "Volume", "androidReliabilityTitle": "Review Android reliability", "closeAction": "Close", "customOption": "Custom", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 89f9c18..b2e29d6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -661,6 +661,7 @@ "alarmScheduleWeekdays": "Días: {days}", "@alarmScheduleWeekdays": {"placeholders": {"days": {}}}, "alarmRepeatSectionLabel": "REPETIR", + "alarmVolumeLabel": "Volumen", "androidReliabilityTitle": "Revisar fiabilidad Android", "closeAction": "Cerrar", "customOption": "Personalizada", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a4c69cc..8e2c1d5 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "RÉPÉTER", + "alarmVolumeLabel": "Volume", "androidReliabilityTitle": "Vérifier la fiabilité Android", "closeAction": "Fermer", "customOption": "Personnalisée", diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index bfbde01..a7564a3 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "दोहराएं", + "alarmVolumeLabel": "आवाज़", "androidReliabilityTitle": "Android विश्वसनीयता जाँचें", "closeAction": "बंद करें", "customOption": "कस्टम", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 1597da7..47c8ddc 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "ULANGI", + "alarmVolumeLabel": "Volume", "androidReliabilityTitle": "Tinjau keandalan Android", "closeAction": "Tutup", "customOption": "Kustom", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 4ab2750..4461135 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "RIPETI", + "alarmVolumeLabel": "Volume", "androidReliabilityTitle": "Controlla affidabilità Android", "closeAction": "Chiudi", "customOption": "Personalizzata", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 995c545..fea1137 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "繰り返し", + "alarmVolumeLabel": "音量", "androidReliabilityTitle": "Androidの信頼性を確認", "closeAction": "閉じる", "customOption": "カスタム", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index b50e602..eba20e4 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "REPETIÇÃO", + "alarmVolumeLabel": "Volume", "androidReliabilityTitle": "Revisar confiabilidade Android", "closeAction": "Fechar", "customOption": "Personalizada", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 3f793f5..9fa6b11 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "ПОВТОР", + "alarmVolumeLabel": "Громкость", "androidReliabilityTitle": "Проверить надёжность Android", "closeAction": "Закрыть", "customOption": "Своя", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 4d4c8d5..d7c653a 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -594,6 +594,7 @@ } }, "alarmRepeatSectionLabel": "重复", + "alarmVolumeLabel": "音量", "androidReliabilityTitle": "检查 Android 可靠性", "closeAction": "关闭", "customOption": "自定义", diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index bcf307b..21928c1 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -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: diff --git a/lib/l10n/gen/app_localizations_ar.dart b/lib/l10n/gen/app_localizations_ar.dart index f98798f..92ad992 100644 --- a/lib/l10n/gen/app_localizations_ar.dart +++ b/lib/l10n/gen/app_localizations_ar.dart @@ -1372,6 +1372,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get alarmRepeatSectionLabel => 'تكرار'; + @override + String get alarmVolumeLabel => 'مستوى الصوت'; + @override String get androidReliabilityTitle => 'مراجعة موثوقية Android'; diff --git a/lib/l10n/gen/app_localizations_bn.dart b/lib/l10n/gen/app_localizations_bn.dart index abb11ff..7c2c4a0 100644 --- a/lib/l10n/gen/app_localizations_bn.dart +++ b/lib/l10n/gen/app_localizations_bn.dart @@ -1381,6 +1381,9 @@ class AppLocalizationsBn extends AppLocalizations { @override String get alarmRepeatSectionLabel => 'পুনরাবৃত্তি'; + @override + String get alarmVolumeLabel => 'ভলিউম'; + @override String get androidReliabilityTitle => 'Android নির্ভরযোগ্যতা দেখুন'; diff --git a/lib/l10n/gen/app_localizations_de.dart b/lib/l10n/gen/app_localizations_de.dart index 3a0e69f..f1a3a46 100644 --- a/lib/l10n/gen/app_localizations_de.dart +++ b/lib/l10n/gen/app_localizations_de.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index 3fa21d4..a48fc1f 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_es.dart b/lib/l10n/gen/app_localizations_es.dart index 1f78cb9..fce8e35 100644 --- a/lib/l10n/gen/app_localizations_es.dart +++ b/lib/l10n/gen/app_localizations_es.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_fr.dart b/lib/l10n/gen/app_localizations_fr.dart index e624d4d..49a6b5f 100644 --- a/lib/l10n/gen/app_localizations_fr.dart +++ b/lib/l10n/gen/app_localizations_fr.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_hi.dart b/lib/l10n/gen/app_localizations_hi.dart index 8518e2d..a15dd55 100644 --- a/lib/l10n/gen/app_localizations_hi.dart +++ b/lib/l10n/gen/app_localizations_hi.dart @@ -1376,6 +1376,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get alarmRepeatSectionLabel => 'दोहराएं'; + @override + String get alarmVolumeLabel => 'आवाज़'; + @override String get androidReliabilityTitle => 'Android विश्वसनीयता जाँचें'; diff --git a/lib/l10n/gen/app_localizations_id.dart b/lib/l10n/gen/app_localizations_id.dart index 9100009..a813041 100644 --- a/lib/l10n/gen/app_localizations_id.dart +++ b/lib/l10n/gen/app_localizations_id.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_it.dart b/lib/l10n/gen/app_localizations_it.dart index f6f8156..07906b9 100644 --- a/lib/l10n/gen/app_localizations_it.dart +++ b/lib/l10n/gen/app_localizations_it.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_ja.dart b/lib/l10n/gen/app_localizations_ja.dart index 52966c1..76d1f95 100644 --- a/lib/l10n/gen/app_localizations_ja.dart +++ b/lib/l10n/gen/app_localizations_ja.dart @@ -1333,6 +1333,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get alarmRepeatSectionLabel => '繰り返し'; + @override + String get alarmVolumeLabel => '音量'; + @override String get androidReliabilityTitle => 'Androidの信頼性を確認'; diff --git a/lib/l10n/gen/app_localizations_pt.dart b/lib/l10n/gen/app_localizations_pt.dart index 36b1217..f6dc84e 100644 --- a/lib/l10n/gen/app_localizations_pt.dart +++ b/lib/l10n/gen/app_localizations_pt.dart @@ -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'; diff --git a/lib/l10n/gen/app_localizations_ru.dart b/lib/l10n/gen/app_localizations_ru.dart index 56f48b5..d006c88 100644 --- a/lib/l10n/gen/app_localizations_ru.dart +++ b/lib/l10n/gen/app_localizations_ru.dart @@ -1385,6 +1385,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get alarmRepeatSectionLabel => 'ПОВТОР'; + @override + String get alarmVolumeLabel => 'Громкость'; + @override String get androidReliabilityTitle => 'Проверить надёжность Android'; diff --git a/lib/l10n/gen/app_localizations_zh.dart b/lib/l10n/gen/app_localizations_zh.dart index 9c6baba..8923f8e 100644 --- a/lib/l10n/gen/app_localizations_zh.dart +++ b/lib/l10n/gen/app_localizations_zh.dart @@ -1328,6 +1328,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get alarmRepeatSectionLabel => '重复'; + @override + String get alarmVolumeLabel => '音量'; + @override String get androidReliabilityTitle => '检查 Android 可靠性'; diff --git a/lib/pantallas/pantalla_alarmas.dart b/lib/pantallas/pantalla_alarmas.dart index 40b70b6..593b518 100644 --- a/lib/pantallas/pantalla_alarmas.dart +++ b/lib/pantallas/pantalla_alarmas.dart @@ -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( - 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( + 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 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}); diff --git a/test/pantallas/pantalla_alarmas_editor_test.dart b/test/pantallas/pantalla_alarmas_editor_test.dart index 01c6ef7..fbfefd7 100644 --- a/test/pantallas/pantalla_alarmas_editor_test.dart +++ b/test/pantallas/pantalla_alarmas_editor_test.dart @@ -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', () {