Files
pluriwave/lib/modelos/emisora.dart
Kira (Agent) e9d1f67aa4
Some checks failed
Flutter CI/CD — PluriWave / Test + Build (pull_request) Has been cancelled
feat(mvp): PluriWave Fase 1 — estructura completa de la app
- Modelo Emisora: campos completos Radio Browser API (fromApi + fromMap)
- ServicioRadio: cliente Radio Browser API (populares, tendencias, buscar por nombre/país/idioma/tag)
- ServicioAudio: just_audio + audio_service wrapper (play/pause/stop/toggle, fade, background handler)
- ServicioTimer: countdown con fade out gradual (15/30/60/90 min)
- ServicioFavoritos: actualizado a v2 con campos codec/bitrate/votes/clickcount
- EstadoRadio: ChangeNotifier global con Provider
- PantallaInicio: grid emisoras populares, chips género, shimmer loading, pull-to-refresh
- PantallaBuscar: SearchBar + filtros país/idioma, lista resultados
- PantallaFavoritos: ReorderableListView + swipe-to-delete (Dismissible)
- TarjetaEmisora: card + modo compacto ListTile, cached_network_image, shimmer fallback
- MiniReproductor: barra inferior persistente con stream de estado
- app.dart: MaterialApp + Provider + NavigationBar + timer dialog
- main.dart: punto de entrada limpio
- AndroidManifest.xml: permisos INTERNET + FOREGROUND_SERVICE + audio_service receivers
2026-04-04 17:15:18 +02:00

150 lines
4.1 KiB
Dart

/// Modelo de datos de una emisora de radio.
///
/// Unifica los campos de la Radio Browser API con los de la tabla SQLite
/// de favoritos. Los campos opcionales pueden ser null cuando la emisora
/// no dispone de esa información.
class Emisora {
final int? id;
final String uuid;
final String nombre;
final String url;
final String? favicon;
final String? pais;
final String? codigoPais; // ISO 3166-1 alpha-2
final String? idioma;
final String? tags;
final String? codec;
final int? bitrate;
final int votes;
final int clickcount;
final int orden;
const Emisora({
this.id,
required this.uuid,
required this.nombre,
required this.url,
this.favicon,
this.pais,
this.codigoPais,
this.idioma,
this.tags,
this.codec,
this.bitrate,
this.votes = 0,
this.clickcount = 0,
this.orden = 0,
});
/// Construye una [Emisora] desde la respuesta JSON de Radio Browser API.
factory Emisora.fromApi(Map<String, dynamic> json) {
return Emisora(
uuid: json['stationuuid'] as String? ?? '',
nombre: json['name'] as String? ?? 'Sin nombre',
url: json['url_resolved'] as String? ?? json['url'] as String? ?? '',
favicon: _nonEmpty(json['favicon'] as String?),
pais: _nonEmpty(json['country'] as String?),
codigoPais: _nonEmpty(json['countrycode'] as String?),
idioma: _nonEmpty(json['language'] as String?),
tags: _nonEmpty(json['tags'] as String?),
codec: _nonEmpty(json['codec'] as String?),
bitrate: json['bitrate'] as int?,
votes: json['votes'] as int? ?? 0,
clickcount: json['clickcount'] as int? ?? 0,
);
}
/// Construye una [Emisora] desde una fila de la tabla `favoritos`.
factory Emisora.fromMap(Map<String, dynamic> map) {
return Emisora(
id: map['id'] as int?,
uuid: map['uuid'] as String,
nombre: map['nombre'] as String,
url: map['url'] as String,
favicon: map['favicon'] as String?,
pais: map['pais'] as String?,
codigoPais: map['codigo_pais'] as String?,
idioma: map['idioma'] as String?,
tags: map['tags'] as String?,
codec: map['codec'] as String?,
bitrate: map['bitrate'] as int?,
votes: map['votes'] as int? ?? 0,
clickcount: map['clickcount'] as int? ?? 0,
orden: map['orden'] as int? ?? 0,
);
}
/// Serializa para inserción/actualización en SQLite.
Map<String, dynamic> toMap() {
return {
'uuid': uuid,
'nombre': nombre,
'url': url,
'favicon': favicon,
'pais': pais,
'codigo_pais': codigoPais,
'idioma': idioma,
'tags': tags,
'codec': codec,
'bitrate': bitrate,
'votes': votes,
'clickcount': clickcount,
'orden': orden,
};
}
Emisora copyWith({
int? id,
String? uuid,
String? nombre,
String? url,
String? favicon,
String? pais,
String? codigoPais,
String? idioma,
String? tags,
String? codec,
int? bitrate,
int? votes,
int? clickcount,
int? orden,
}) {
return Emisora(
id: id ?? this.id,
uuid: uuid ?? this.uuid,
nombre: nombre ?? this.nombre,
url: url ?? this.url,
favicon: favicon ?? this.favicon,
pais: pais ?? this.pais,
codigoPais: codigoPais ?? this.codigoPais,
idioma: idioma ?? this.idioma,
tags: tags ?? this.tags,
codec: codec ?? this.codec,
bitrate: bitrate ?? this.bitrate,
votes: votes ?? this.votes,
clickcount: clickcount ?? this.clickcount,
orden: orden ?? this.orden,
);
}
/// Lista de géneros/tags como lista limpia.
List<String> get generos {
if (tags == null || tags!.isEmpty) return [];
return tags!.split(',').map((t) => t.trim()).where((t) => t.isNotEmpty).toList();
}
static String? _nonEmpty(String? s) =>
(s == null || s.trim().isEmpty) ? null : s.trim();
@override
String toString() => 'Emisora(uuid: $uuid, nombre: $nombre)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Emisora && runtimeType == other.runtimeType && uuid == other.uuid;
@override
int get hashCode => uuid.hashCode;
}