Audit 11.4 and 11.6: the band column was 152 instead of 280 (t4 line 581), leaving the sliders 46% short, and the fill read liveGreen -- the LIVE badge colour -- instead of brand teal (t4 line 585). Adds two guards. The existing tests only asserted the band COUNT, which is exactly why both values could drift unnoticed.
213 lines
8.0 KiB
Dart
213 lines
8.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../l10n/gen/app_localizations.dart';
|
|
import '../modelos/preset_ecualizador.dart';
|
|
import '../tema/pluriwave_theme.dart';
|
|
import '../tema/pluriwave_tokens.dart';
|
|
import 'pluri_glass_surface.dart';
|
|
|
|
class EcualizadorWidget extends StatefulWidget {
|
|
final PresetEcualizador preset;
|
|
final void Function(PresetEcualizador) onCambio;
|
|
|
|
/// Design ADR-5: greys and disables every slider when the equalizer
|
|
/// itself is off, instead of leaving fully-interactive controls that
|
|
/// silently do nothing.
|
|
final bool habilitado;
|
|
|
|
const EcualizadorWidget({
|
|
super.key,
|
|
required this.preset,
|
|
required this.onCambio,
|
|
this.habilitado = true,
|
|
});
|
|
|
|
@override
|
|
State<EcualizadorWidget> createState() => _EcualizadorWidgetState();
|
|
}
|
|
|
|
class _EcualizadorWidgetState extends State<EcualizadorWidget> {
|
|
late List<double> _bandas;
|
|
final List<String> _etiquetas = ['60Hz', '250Hz', '1kHz', '4kHz', '16kHz'];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_bandas = List.from(widget.preset.bandas);
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(EcualizadorWidget old) {
|
|
super.didUpdateWidget(old);
|
|
if (old.preset.nombre != widget.preset.nombre) {
|
|
setState(() => _bandas = List.from(widget.preset.bandas));
|
|
}
|
|
}
|
|
|
|
void _actualizarBanda(int index, double valor) {
|
|
setState(() => _bandas[index] = valor);
|
|
widget.onCambio(
|
|
PresetEcualizador(nombre: 'Personalizado', bandas: List.from(_bandas)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final tokens = context.pluriTokens;
|
|
final l10n = AppLocalizations.of(context);
|
|
|
|
// Design ADR-5: the title + preset Chip row that used to live here is
|
|
// gone — the pushed screen's own 56px header carries the title now, and
|
|
// the preset chip ROW (a different, still-reusable widget,
|
|
// [PresetsEcualizadorWidget] below) is rendered by the caller alongside
|
|
// this widget instead of being duplicated inside it.
|
|
return PluriGlassSurface(
|
|
borderRadius: BorderRadius.circular(tokens.radiusLg),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
for (int i = 0; i < 5; i++)
|
|
Expanded(
|
|
child: AnimatedOpacity(
|
|
duration: const Duration(milliseconds: 150),
|
|
opacity: widget.habilitado ? 1.0 : 0.4,
|
|
child: Card(
|
|
color: tokens.listSurface.withValues(alpha: 0.6),
|
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(tokens.radiusSm),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 10,
|
|
horizontal: 4,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
SizedBox(
|
|
// Audit 11.4: the prototype draws the band column at
|
|
// 280 (t4 line 581); 152 left the sliders 46% short.
|
|
height: 280,
|
|
child: Semantics(
|
|
slider: true,
|
|
enabled: widget.habilitado,
|
|
label: l10n.equalizerBandLabel(_etiquetas[i]),
|
|
value: l10n.equalizerBandValue(
|
|
_bandas[i].toStringAsFixed(1),
|
|
),
|
|
child: RotatedBox(
|
|
quarterTurns: 3,
|
|
child: SliderTheme(
|
|
data: SliderTheme.of(context).copyWith(
|
|
trackHeight: 5,
|
|
// Audit 11.6: the prototype's band fill is
|
|
// brand teal (t4 line 585). `liveGreen` is
|
|
// the LIVE badge colour and reading it here
|
|
// put the sliders in the wrong colour family.
|
|
activeTrackColor: PluriWaveTokens.brand,
|
|
thumbColor: PluriWaveTokens.brand,
|
|
inactiveTrackColor:
|
|
theme.colorScheme.surfaceContainerHighest,
|
|
overlayColor: PluriWaveTokens.brand
|
|
.withValues(alpha: 0.15),
|
|
),
|
|
child: Slider(
|
|
value: _bandas[i],
|
|
min: -12.0,
|
|
max: 12.0,
|
|
divisions: 24,
|
|
onChanged:
|
|
widget.habilitado
|
|
? (v) => _actualizarBanda(i, v)
|
|
: null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Text(
|
|
'${_bandas[i].toStringAsFixed(1)}dB',
|
|
style: theme.textTheme.labelSmall?.copyWith(
|
|
color: tokens.liveGreen,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
Text(
|
|
_etiquetas[i],
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
String _nombrePreset(AppLocalizations l10n, String nombre) {
|
|
return switch (nombre) {
|
|
'Flat' => l10n.equalizerPresetFlat,
|
|
'Rock' => l10n.equalizerPresetRock,
|
|
'Pop' => l10n.equalizerPresetPop,
|
|
'Bass Boost' => l10n.equalizerPresetBassBoost,
|
|
'Jazz' => l10n.equalizerPresetJazz,
|
|
'Voz' => l10n.equalizerPresetVoice,
|
|
'Personalizado' => l10n.equalizerPresetCustom,
|
|
_ => nombre,
|
|
};
|
|
}
|
|
|
|
class PresetsEcualizadorWidget extends StatelessWidget {
|
|
final PresetEcualizador presetActual;
|
|
final void Function(PresetEcualizador) onSeleccionar;
|
|
|
|
/// User-saved custom presets (WU13, `eq-custom-presets` spec), appended
|
|
/// after the 6 fixed ones. Additive-only parameter — defaults to empty so
|
|
/// this stays the same widget, not a new one (design ADR-5: "stays
|
|
/// as-is" means no restyle, not that it can never gain new data to show).
|
|
/// `_nombrePreset`'s default case already falls through to the raw name,
|
|
/// so a custom preset's chip label needs no special-casing here.
|
|
final List<PresetEcualizador> personalizados;
|
|
|
|
const PresetsEcualizadorWidget({
|
|
super.key,
|
|
required this.presetActual,
|
|
required this.onSeleccionar,
|
|
this.personalizados = const [],
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final l10n = AppLocalizations.of(context);
|
|
final todos = [...PresetEcualizador.presets, ...personalizados];
|
|
return Wrap(
|
|
spacing: 8,
|
|
runSpacing: 6,
|
|
children:
|
|
todos.map((p) {
|
|
final selected = p.nombre == presetActual.nombre;
|
|
return ChoiceChip(
|
|
label: Text(_nombrePreset(l10n, p.nombre)),
|
|
selected: selected,
|
|
showCheckmark: false,
|
|
selectedColor: theme.colorScheme.primaryContainer,
|
|
backgroundColor: theme.colorScheme.surfaceContainerHighest
|
|
.withValues(alpha: 0.32),
|
|
onSelected: (_) => onSeleccionar(p),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
}
|