fix(eq): seed device ID at startup and add device management UI

Fix multi-device EQ auto-switching by calling obtenerDispositivoActual()
during cargarPersistido() to seed the initial device ID. Add device
management modal with rename support, EQ preset editing, and connection
status indicator. Translate device UI keys to all 13 locales.
This commit is contained in:
2026-06-28 11:55:15 +02:00
parent 71978de68f
commit 58922de6fc
34 changed files with 1461 additions and 51 deletions
+160 -27
View File
@@ -16,6 +16,7 @@ import '../l10n/display_names.dart';
import '../l10n/gen/app_localizations.dart';
import '../modelos/emisora.dart';
import '../modelos/grupo_favoritos.dart';
import '../modelos/preset_ecualizador.dart';
import '../widgets/ecualizador_widget.dart';
import '../widgets/pluri_glass_surface.dart';
import '../widgets/pluri_icon.dart';
@@ -739,33 +740,9 @@ class _SeccionEcualizadorAvanzado extends StatelessWidget {
)
else
for (final entry in presetsDispositivo.entries)
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
const Icon(Icons.headphones_rounded, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
entry.key,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
Text(
l10n.advancedEqDevicePresetLabel(
entry.value.nombre,
),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
_FilaDispositivo(
deviceId: entry.key,
preset: entry.value,
),
],
],
@@ -774,6 +751,162 @@ class _SeccionEcualizadorAvanzado extends StatelessWidget {
}
}
/// A single device row in the known-devices list.
///
/// Shows a connection indicator (green dot) when [deviceId] matches the
/// currently active device. Tapping the edit icon opens [_DialogoEdicionDispositivo].
class _FilaDispositivo extends StatelessWidget {
const _FilaDispositivo({required this.deviceId, required this.preset});
final String deviceId;
final PresetEcualizador preset;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final eq = context.watch<EstadoEcualizador>();
final isActive = eq.dispositivoActualId == deviceId;
final displayName = eq.nombreVisible(deviceId, '');
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
if (isActive)
const Icon(Icons.circle, size: 10, color: Colors.green)
else
const SizedBox(width: 10),
const SizedBox(width: 8),
const Icon(Icons.headphones_rounded, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
Text(
l10n.advancedEqDevicePresetLabel(preset.nombre),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
IconButton(
icon: const Icon(Icons.edit_rounded, size: 20),
tooltip: l10n.eqDeviceEditTitle,
onPressed: () => _abrirModal(context),
),
],
),
);
}
Future<void> _abrirModal(BuildContext context) async {
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
showDragHandle: true,
builder: (ctx) => _DialogoEdicionDispositivo(
deviceId: deviceId,
preset: preset,
),
);
}
}
/// Bottom sheet for editing a device's custom name and EQ preset.
class _DialogoEdicionDispositivo extends StatefulWidget {
const _DialogoEdicionDispositivo({
required this.deviceId,
required this.preset,
});
final String deviceId;
final PresetEcualizador preset;
@override
State<_DialogoEdicionDispositivo> createState() =>
_DialogoEdicionDispositivoState();
}
class _DialogoEdicionDispositivoState
extends State<_DialogoEdicionDispositivo> {
late final TextEditingController _nombreCtrl;
late PresetEcualizador _presetActual;
@override
void initState() {
super.initState();
final eq = context.read<EstadoEcualizador>();
final displayName = eq.nombreVisible(widget.deviceId, '');
_nombreCtrl = TextEditingController(text: displayName);
_presetActual = widget.preset;
}
@override
void dispose() {
_nombreCtrl.dispose();
super.dispose();
}
Future<void> _guardar() async {
final eq = context.read<EstadoEcualizador>();
await eq.renombrarDispositivo(widget.deviceId, _nombreCtrl.text);
if (_presetActual != widget.preset) {
await eq.guardarPresetDispositivo(widget.deviceId, _presetActual);
}
if (mounted) Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final bottom = MediaQuery.viewInsetsOf(context).bottom;
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.eqDeviceEditTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
controller: _nombreCtrl,
autofocus: true,
decoration: InputDecoration(
labelText: l10n.eqDeviceNameLabel,
hintText: l10n.eqDeviceNameHint,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
EcualizadorWidget(
preset: _presetActual,
onCambio: (p) => setState(() => _presetActual = p),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _guardar,
icon: const Icon(Icons.save_rounded),
label: Text(l10n.eqDeviceNameConfirm),
),
],
),
),
);
}
}
class _SeccionOrdenListas extends StatelessWidget {
const _SeccionOrdenListas();