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:
@@ -51,6 +51,9 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
/// Matrix presets: "stationUuid:deviceId" → PresetEcualizador.
|
||||
final Map<String, PresetEcualizador> _presetsMatriz = {};
|
||||
|
||||
/// Custom display names for devices: deviceId → custom name.
|
||||
final Map<String, String> _nombresDispositivos = {};
|
||||
|
||||
PresetEcualizador _presetPrincipal = PresetEcualizador.flat;
|
||||
PresetEcualizador _presetActual = PresetEcualizador.flat;
|
||||
bool _activo = true;
|
||||
@@ -65,6 +68,9 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
bool get eqMultiDeviceEnabled => _eqMultiDeviceEnabled;
|
||||
String? get dispositivoActualId => _dispositivoActualId;
|
||||
|
||||
Map<String, String> get nombresDispositivos =>
|
||||
Map.unmodifiable(_nombresDispositivos);
|
||||
|
||||
Map<String, PresetEcualizador> get presetsPorEmisora =>
|
||||
Map.unmodifiable(_presetsEmisoraMap);
|
||||
|
||||
@@ -148,6 +154,9 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
_presetsMatriz
|
||||
..clear()
|
||||
..addAll(config.presetsMatriz);
|
||||
_nombresDispositivos
|
||||
..clear()
|
||||
..addAll(config.nombresDispositivos);
|
||||
|
||||
// Resolve active preset and apply it.
|
||||
_presetActual = _resolverPresetActivo();
|
||||
@@ -156,6 +165,11 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
|
||||
// Subscribe to device changes only when toggle is on.
|
||||
_configurarSuscripcionDispositivo();
|
||||
|
||||
// Seed current device immediately so presets resolve before first event.
|
||||
if (_eqMultiDeviceEnabled) {
|
||||
await _sembrarDispositivoActual();
|
||||
}
|
||||
} catch (_) {
|
||||
_presetPrincipal = PresetEcualizador.flat;
|
||||
_presetActual = PresetEcualizador.flat;
|
||||
@@ -164,6 +178,20 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
_presetsEmisoraMap.clear();
|
||||
_presetsDispositivo.clear();
|
||||
_presetsMatriz.clear();
|
||||
_nombresDispositivos.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries the current device and seeds [_dispositivoActualId] without
|
||||
/// waiting for a stream event. Falls back to `'builtin_speaker'` on error.
|
||||
Future<void> _sembrarDispositivoActual() async {
|
||||
final svc = _dispositivoAudio;
|
||||
if (svc == null) return;
|
||||
try {
|
||||
final dispositivo = await svc.obtenerDispositivoActual();
|
||||
await _onDispositivoCambiado(dispositivo);
|
||||
} catch (_) {
|
||||
_dispositivoActualId = 'builtin_speaker';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +308,55 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Persists a custom display name for [deviceId].
|
||||
///
|
||||
/// Empty names are silently ignored so the existing name is preserved.
|
||||
/// No-op when the multi-device toggle is off.
|
||||
Future<void> renombrarDispositivo(String deviceId, String nombre) async {
|
||||
if (!_eqMultiDeviceEnabled) return;
|
||||
final nombreTrimmed = nombre.trim();
|
||||
if (nombreTrimmed.isEmpty) return;
|
||||
_nombresDispositivos[deviceId] = nombreTrimmed;
|
||||
await servicio.guardarNombresDispositivos(
|
||||
Map.unmodifiable(_nombresDispositivos),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Persists a per-device EQ preset for [deviceId].
|
||||
///
|
||||
/// Guards on [_eqMultiDeviceEnabled]: no-op when toggle is off.
|
||||
/// If [deviceId] is the currently active device, re-resolves and
|
||||
/// applies the effective preset immediately.
|
||||
Future<void> guardarPresetDispositivo(
|
||||
String deviceId,
|
||||
PresetEcualizador preset,
|
||||
) async {
|
||||
if (!_eqMultiDeviceEnabled) return;
|
||||
_presetsDispositivo[deviceId] = preset;
|
||||
await servicio.guardarPresetDispositivo(deviceId, preset);
|
||||
if (_dispositivoActualId == deviceId) {
|
||||
// Re-resolve using the full hierarchy (station/matrix may override).
|
||||
final resuelto = _resolverPresetActivo();
|
||||
_presetActual = resuelto;
|
||||
await audio.aplicarPreset(resuelto);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Returns the stored custom name for [deviceId], or an empty string if none.
|
||||
String obtenerNombreDispositivo(String deviceId) =>
|
||||
_nombresDispositivos[deviceId] ?? '';
|
||||
|
||||
/// Resolves the display name for [deviceId] using the fallback chain:
|
||||
/// custom name → [platformName] → raw [deviceId].
|
||||
String nombreVisible(String deviceId, String platformName) {
|
||||
final custom = _nombresDispositivos[deviceId];
|
||||
if (custom != null && custom.isNotEmpty) return custom;
|
||||
if (platformName.isNotEmpty) return platformName;
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
Future<void> cambiarActivo(bool activo) async {
|
||||
_activo = activo;
|
||||
await servicio.guardarActivo(activo);
|
||||
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "يبدأ خلال {minutes} دقيقة",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "تعديل الجهاز",
|
||||
"eqDeviceNameLabel": "اسم الجهاز",
|
||||
"eqDeviceNameHint": "مثال: مكبر صوت غرفة المعيشة",
|
||||
"eqDeviceNameConfirm": "حفظ",
|
||||
"eqDeviceConnected": "متصل"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "{minutes} মিনিটে শুরু হবে",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "ডিভাইস সম্পাদনা করুন",
|
||||
"eqDeviceNameLabel": "ডিভাইসের নাম",
|
||||
"eqDeviceNameHint": "যেমন: লিভিং রুমের স্পিকার",
|
||||
"eqDeviceNameConfirm": "সংরক্ষণ করুন",
|
||||
"eqDeviceConnected": "সংযুক্ত"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Startet in {minutes} Min.",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Gerät bearbeiten",
|
||||
"eqDeviceNameLabel": "Gerätename",
|
||||
"eqDeviceNameHint": "z. B. Wohnzimmer-Lautsprecher",
|
||||
"eqDeviceNameConfirm": "Speichern",
|
||||
"eqDeviceConnected": "Verbunden"
|
||||
}
|
||||
+14
-1
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Starts in {minutes} min",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Edit device",
|
||||
"eqDeviceNameLabel": "Device name",
|
||||
"eqDeviceNameHint": "e.g. Living Room Speaker",
|
||||
"eqDeviceNameConfirm": "Save",
|
||||
"eqDeviceConnected": "Connected"
|
||||
}
|
||||
|
||||
+14
-1
@@ -590,5 +590,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Empieza en {minutes} min",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Editar dispositivo",
|
||||
"eqDeviceNameLabel": "Nombre del dispositivo",
|
||||
"eqDeviceNameHint": "Ej: Altavoz del living",
|
||||
"eqDeviceNameConfirm": "Guardar",
|
||||
"eqDeviceConnected": "Conectado"
|
||||
}
|
||||
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Démarre dans {minutes} min",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Modifier l'appareil",
|
||||
"eqDeviceNameLabel": "Nom de l'appareil",
|
||||
"eqDeviceNameHint": "ex. : Enceinte salon",
|
||||
"eqDeviceNameConfirm": "Enregistrer",
|
||||
"eqDeviceConnected": "Connecté"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "{minutes} मिनट में शुरू होगा",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "डिवाइस संपादित करें",
|
||||
"eqDeviceNameLabel": "डिवाइस का नाम",
|
||||
"eqDeviceNameHint": "उदा. लिविंग रूम स्पीकर",
|
||||
"eqDeviceNameConfirm": "सहेजें",
|
||||
"eqDeviceConnected": "कनेक्टेड"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Mulai dalam {minutes} menit",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Edit perangkat",
|
||||
"eqDeviceNameLabel": "Nama perangkat",
|
||||
"eqDeviceNameHint": "Mis: Speaker ruang tamu",
|
||||
"eqDeviceNameConfirm": "Simpan",
|
||||
"eqDeviceConnected": "Terhubung"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Inizia tra {minutes} min",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Modifica dispositivo",
|
||||
"eqDeviceNameLabel": "Nome del dispositivo",
|
||||
"eqDeviceNameHint": "Es: Cassa del salotto",
|
||||
"eqDeviceNameConfirm": "Salva",
|
||||
"eqDeviceConnected": "Connesso"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "{minutes}分後に開始",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "デバイスを編集",
|
||||
"eqDeviceNameLabel": "デバイス名",
|
||||
"eqDeviceNameHint": "例:リビングのスピーカー",
|
||||
"eqDeviceNameConfirm": "保存",
|
||||
"eqDeviceConnected": "接続中"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Começa em {minutes} min",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Editar dispositivo",
|
||||
"eqDeviceNameLabel": "Nome do dispositivo",
|
||||
"eqDeviceNameHint": "Ex: Caixa da sala",
|
||||
"eqDeviceNameConfirm": "Salvar",
|
||||
"eqDeviceConnected": "Conectado"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "Начнётся через {minutes} мин",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "Изменить устройство",
|
||||
"eqDeviceNameLabel": "Название устройства",
|
||||
"eqDeviceNameHint": "Напр.: Колонка в гостиной",
|
||||
"eqDeviceNameConfirm": "Сохранить",
|
||||
"eqDeviceConnected": "Подключено"
|
||||
}
|
||||
+15
-2
@@ -627,5 +627,18 @@
|
||||
"placeholders": {
|
||||
"presetName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preNoticeCountdown": "{minutes}分钟后开始",
|
||||
"@preNoticeCountdown": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"eqDeviceEditTitle": "编辑设备",
|
||||
"eqDeviceNameLabel": "设备名称",
|
||||
"eqDeviceNameHint": "例:客厅音箱",
|
||||
"eqDeviceNameConfirm": "保存",
|
||||
"eqDeviceConnected": "已连接"
|
||||
}
|
||||
@@ -2305,6 +2305,42 @@ abstract class AppLocalizations {
|
||||
/// In es, this message translates to:
|
||||
/// **'Preset: {presetName}'**
|
||||
String advancedEqDevicePresetLabel(Object presetName);
|
||||
|
||||
/// No description provided for @preNoticeCountdown.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Empieza en {minutes} min'**
|
||||
String preNoticeCountdown(int minutes);
|
||||
|
||||
/// No description provided for @eqDeviceEditTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Editar dispositivo'**
|
||||
String get eqDeviceEditTitle;
|
||||
|
||||
/// No description provided for @eqDeviceNameLabel.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Nombre del dispositivo'**
|
||||
String get eqDeviceNameLabel;
|
||||
|
||||
/// No description provided for @eqDeviceNameHint.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Ej: Altavoz del living'**
|
||||
String get eqDeviceNameHint;
|
||||
|
||||
/// No description provided for @eqDeviceNameConfirm.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Guardar'**
|
||||
String get eqDeviceNameConfirm;
|
||||
|
||||
/// No description provided for @eqDeviceConnected.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Conectado'**
|
||||
String get eqDeviceConnected;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -1247,4 +1247,24 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'الإعداد المسبق: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'يبدأ خلال $minutes دقيقة';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'تعديل الجهاز';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'اسم الجهاز';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'مثال: مكبر صوت غرفة المعيشة';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'حفظ';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'متصل';
|
||||
}
|
||||
|
||||
@@ -1254,4 +1254,24 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'প্রিসেট: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return '$minutes মিনিটে শুরু হবে';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'ডিভাইস সম্পাদনা করুন';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'ডিভাইসের নাম';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'যেমন: লিভিং রুমের স্পিকার';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'সংরক্ষণ করুন';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'সংযুক্ত';
|
||||
}
|
||||
|
||||
@@ -1264,4 +1264,24 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Startet in $minutes Min.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Gerät bearbeiten';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Gerätename';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'z. B. Wohnzimmer-Lautsprecher';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Speichern';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Verbunden';
|
||||
}
|
||||
|
||||
@@ -1250,4 +1250,24 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Starts in $minutes min';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Edit device';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Device name';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'e.g. Living Room Speaker';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Save';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Connected';
|
||||
}
|
||||
|
||||
@@ -1259,4 +1259,24 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Empieza en $minutes min';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Editar dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Nombre del dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'Ej: Altavoz del living';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Guardar';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Conectado';
|
||||
}
|
||||
|
||||
@@ -1269,4 +1269,24 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset : $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Démarre dans $minutes min';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Modifier l\'appareil';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Nom de l\'appareil';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'ex. : Enceinte salon';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Enregistrer';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Connecté';
|
||||
}
|
||||
|
||||
@@ -1253,4 +1253,24 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'प्रीसेट: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return '$minutes मिनट में शुरू होगा';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'डिवाइस संपादित करें';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'डिवाइस का नाम';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'उदा. लिविंग रूम स्पीकर';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'सहेजें';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'कनेक्टेड';
|
||||
}
|
||||
|
||||
@@ -1258,4 +1258,24 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Mulai dalam $minutes menit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Edit perangkat';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Nama perangkat';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'Mis: Speaker ruang tamu';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Simpan';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Terhubung';
|
||||
}
|
||||
|
||||
@@ -1264,4 +1264,24 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Inizia tra $minutes min';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Modifica dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Nome del dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'Es: Cassa del salotto';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Salva';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Connesso';
|
||||
}
|
||||
|
||||
@@ -1214,4 +1214,24 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'プリセット: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return '$minutes分後に開始';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'デバイスを編集';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'デバイス名';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => '例:リビングのスピーカー';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => '保存';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => '接続中';
|
||||
}
|
||||
|
||||
@@ -1256,4 +1256,24 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Preset: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Começa em $minutes min';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Editar dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Nome do dispositivo';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'Ex: Caixa da sala';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Salvar';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Conectado';
|
||||
}
|
||||
|
||||
@@ -1260,4 +1260,24 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return 'Пресет: $presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return 'Начнётся через $minutes мин';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => 'Изменить устройство';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => 'Название устройства';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => 'Напр.: Колонка в гостиной';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => 'Сохранить';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => 'Подключено';
|
||||
}
|
||||
|
||||
@@ -1207,4 +1207,24 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
String advancedEqDevicePresetLabel(Object presetName) {
|
||||
return '预设:$presetName';
|
||||
}
|
||||
|
||||
@override
|
||||
String preNoticeCountdown(int minutes) {
|
||||
return '$minutes分钟后开始';
|
||||
}
|
||||
|
||||
@override
|
||||
String get eqDeviceEditTitle => '编辑设备';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameLabel => '设备名称';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameHint => '例:客厅音箱';
|
||||
|
||||
@override
|
||||
String get eqDeviceNameConfirm => '保存';
|
||||
|
||||
@override
|
||||
String get eqDeviceConnected => '已连接';
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class ConfiguracionEcualizador {
|
||||
this.eqMultiDeviceEnabled = false,
|
||||
this.presetsDispositivo = const {},
|
||||
this.presetsMatriz = const {},
|
||||
this.nombresDispositivos = const {},
|
||||
});
|
||||
|
||||
final PresetEcualizador principal;
|
||||
@@ -26,6 +27,9 @@ class ConfiguracionEcualizador {
|
||||
|
||||
/// Matrix presets: "stationUuid:deviceId" → PresetEcualizador.
|
||||
final Map<String, PresetEcualizador> presetsMatriz;
|
||||
|
||||
/// Custom display names for devices: deviceId → custom name.
|
||||
final Map<String, String> nombresDispositivos;
|
||||
}
|
||||
|
||||
class ServicioEcualizador {
|
||||
@@ -37,6 +41,7 @@ class ServicioEcualizador {
|
||||
static const _keyMultiDeviceEnabled = 'eq_multi_device_enabled_v1';
|
||||
static const _keyPresetsPorDispositivo = 'eq_preset_por_dispositivo_v1';
|
||||
static const _keyPresetsMatriz = 'eq_presets_matriz_v1';
|
||||
static const _keyNombresDispositivos = 'eq_nombres_dispositivos_v1';
|
||||
|
||||
final SharedPreferences? _prefs;
|
||||
|
||||
@@ -50,6 +55,7 @@ class ServicioEcualizador {
|
||||
final porEmisora = _leerPresetsPorEmisora(prefs);
|
||||
final presetsDispositivo = _leerMapa(prefs, _keyPresetsPorDispositivo);
|
||||
final presetsMatriz = _leerMapa(prefs, _keyPresetsMatriz);
|
||||
final nombresDispositivos = _leerMapaStrings(prefs, _keyNombresDispositivos);
|
||||
return ConfiguracionEcualizador(
|
||||
principal: principal,
|
||||
porEmisora: porEmisora,
|
||||
@@ -57,6 +63,7 @@ class ServicioEcualizador {
|
||||
eqMultiDeviceEnabled: prefs.getBool(_keyMultiDeviceEnabled) ?? false,
|
||||
presetsDispositivo: presetsDispositivo,
|
||||
presetsMatriz: presetsMatriz,
|
||||
nombresDispositivos: nombresDispositivos,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,6 +106,11 @@ class ServicioEcualizador {
|
||||
config.presetsDispositivo,
|
||||
);
|
||||
await _guardarMapa(prefs, _keyPresetsMatriz, config.presetsMatriz);
|
||||
await _guardarMapaStrings(
|
||||
prefs,
|
||||
_keyNombresDispositivos,
|
||||
config.nombresDispositivos,
|
||||
);
|
||||
}
|
||||
|
||||
/// Persists the multi-device feature toggle.
|
||||
@@ -145,6 +157,18 @@ class ServicioEcualizador {
|
||||
await _guardarMapa(prefs, _keyPresetsMatriz, mapa);
|
||||
}
|
||||
|
||||
/// Returns the persisted device custom names map.
|
||||
Future<Map<String, String>> cargarNombresDispositivos() async {
|
||||
final prefs = await _resolverPrefs();
|
||||
return _leerMapaStrings(prefs, _keyNombresDispositivos);
|
||||
}
|
||||
|
||||
/// Persists the device custom names map.
|
||||
Future<void> guardarNombresDispositivos(Map<String, String> nombres) async {
|
||||
final prefs = await _resolverPrefs();
|
||||
await _guardarMapaStrings(prefs, _keyNombresDispositivos, nombres);
|
||||
}
|
||||
|
||||
/// Reads a `Map<String, PresetEcualizador>` from a SharedPreferences JSON key.
|
||||
Map<String, PresetEcualizador> _leerMapa(
|
||||
SharedPreferences prefs,
|
||||
@@ -174,6 +198,26 @@ class ServicioEcualizador {
|
||||
await prefs.setString(key, jsonEncode(serializado));
|
||||
}
|
||||
|
||||
/// Reads a `Map<String, String>` from a SharedPreferences JSON key.
|
||||
Map<String, String> _leerMapaStrings(SharedPreferences prefs, String key) {
|
||||
final raw = prefs.getString(key);
|
||||
if (raw == null || raw.isEmpty) return {};
|
||||
try {
|
||||
final data = Map<String, dynamic>.from(jsonDecode(raw) as Map);
|
||||
return data.map((k, v) => MapEntry(k, v as String));
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _guardarMapaStrings(
|
||||
SharedPreferences prefs,
|
||||
String key,
|
||||
Map<String, String> mapa,
|
||||
) async {
|
||||
await prefs.setString(key, jsonEncode(mapa));
|
||||
}
|
||||
|
||||
PresetEcualizador _leerPresetPrincipal(SharedPreferences prefs) {
|
||||
final raw = prefs.getString(_keyPresetPrincipal);
|
||||
if (raw == null || raw.isEmpty) {
|
||||
|
||||
Reference in New Issue
Block a user