feat(mvp): PluriWave Fase 1 — estructura completa de la app
Some checks failed
Flutter CI/CD — PluriWave / Test + Build (pull_request) Has been cancelled
Some checks failed
Flutter CI/CD — PluriWave / Test + Build (pull_request) Has been cancelled
- 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
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
/// Modelo de datos de una emisora de radio.
|
||||
///
|
||||
/// Representa una emisora favorita almacenada en SQLite.
|
||||
/// Los campos opcionales (favicon, pais, idioma, tags) pueden ser null
|
||||
/// cuando la emisora no dispone de esa información.
|
||||
/// 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;
|
||||
@@ -10,8 +10,13 @@ class Emisora {
|
||||
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({
|
||||
@@ -21,11 +26,34 @@ class Emisora {
|
||||
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(
|
||||
@@ -35,14 +63,18 @@ class Emisora {
|
||||
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 la emisora para inserción/actualización en SQLite.
|
||||
/// No incluye [id] — lo gestiona la BD.
|
||||
/// Serializa para inserción/actualización en SQLite.
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
@@ -50,13 +82,17 @@ class Emisora {
|
||||
'url': url,
|
||||
'favicon': favicon,
|
||||
'pais': pais,
|
||||
'codigo_pais': codigoPais,
|
||||
'idioma': idioma,
|
||||
'tags': tags,
|
||||
'codec': codec,
|
||||
'bitrate': bitrate,
|
||||
'votes': votes,
|
||||
'clickcount': clickcount,
|
||||
'orden': orden,
|
||||
};
|
||||
}
|
||||
|
||||
/// Devuelve una copia con los campos indicados modificados.
|
||||
Emisora copyWith({
|
||||
int? id,
|
||||
String? uuid,
|
||||
@@ -64,8 +100,13 @@ class Emisora {
|
||||
String? url,
|
||||
String? favicon,
|
||||
String? pais,
|
||||
String? codigoPais,
|
||||
String? idioma,
|
||||
String? tags,
|
||||
String? codec,
|
||||
int? bitrate,
|
||||
int? votes,
|
||||
int? clickcount,
|
||||
int? orden,
|
||||
}) {
|
||||
return Emisora(
|
||||
@@ -75,22 +116,33 @@ class Emisora {
|
||||
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(id: $id, uuid: $uuid, nombre: $nombre, orden: $orden)';
|
||||
String toString() => 'Emisora(uuid: $uuid, nombre: $nombre)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Emisora &&
|
||||
runtimeType == other.runtimeType &&
|
||||
uuid == other.uuid;
|
||||
other is Emisora && runtimeType == other.runtimeType && uuid == other.uuid;
|
||||
|
||||
@override
|
||||
int get hashCode => uuid.hashCode;
|
||||
|
||||
Reference in New Issue
Block a user