feat(ui): add premium PluriWave redesign
Build & Deploy Pluriwave / Análisis de código (push) Failing after 21s
Build & Deploy Pluriwave / Build APK + AAB release (push) Has been skipped

This commit is contained in:
2026-05-20 18:42:22 +02:00
parent f95a8290ae
commit c707fc9911
30 changed files with 2218 additions and 954 deletions
+28 -19
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import '../servicios/servicio_audio.dart';
@@ -49,6 +50,7 @@ class _VisualizadorAudioState extends State<VisualizadorAudio>
late List<_BarraState> _barras;
final _random = Random();
bool _activo = false;
StreamSubscription<EstadoReproduccion>? _estadoSubscription;
@override
void initState() {
@@ -68,13 +70,14 @@ class _VisualizadorAudioState extends State<VisualizadorAudio>
duration: const Duration(seconds: 1),
)..addListener(_actualizar);
widget.estadoStream.listen(_onEstado);
_estadoSubscription = widget.estadoStream.listen(_onEstado);
}
void _onEstado(EstadoReproduccion estado) {
final nuevoActivo = estado == EstadoReproduccion.reproduciendo ||
estado == EstadoReproduccion.cargando;
if (nuevoActivo == _activo) return;
if (!mounted) return;
setState(() => _activo = nuevoActivo);
if (nuevoActivo) {
_controller.repeat();
@@ -91,6 +94,7 @@ class _VisualizadorAudioState extends State<VisualizadorAudio>
@override
void dispose() {
_estadoSubscription?.cancel();
_controller.dispose();
super.dispose();
}
@@ -110,10 +114,11 @@ class _VisualizadorAudioState extends State<VisualizadorAudio>
final espaciado = totalAncho / widget.barras;
final anchoBar = (espaciado * 0.55).clamp(2.0, 8.0);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(widget.barras, (i) {
return RepaintBoundary(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(widget.barras, (i) {
final b = _barras[i];
final double altura;
@@ -131,21 +136,22 @@ class _VisualizadorAudioState extends State<VisualizadorAudio>
altura = b.alturaActual.clamp(2.0, widget.altura * 0.05);
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: (espaciado - anchoBar) / 2),
child: AnimatedContainer(
duration: const Duration(milliseconds: 80),
width: anchoBar,
height: altura.clamp(2.0, widget.altura),
decoration: BoxDecoration(
color: color.withValues(
alpha: _activo ? 0.7 + (altura / widget.altura) * 0.3 : 0.3,
return Padding(
padding: EdgeInsets.symmetric(horizontal: (espaciado - anchoBar) / 2),
child: AnimatedContainer(
duration: const Duration(milliseconds: 80),
width: anchoBar,
height: altura.clamp(2.0, widget.altura),
decoration: BoxDecoration(
color: color.withValues(
alpha: _activo ? 0.7 + (altura / widget.altura) * 0.3 : 0.3,
),
borderRadius: BorderRadius.circular(anchoBar / 2),
),
borderRadius: BorderRadius.circular(anchoBar / 2),
),
),
);
}),
);
}),
),
);
},
),
@@ -190,15 +196,17 @@ class _IndicadorReproduccionState extends State<IndicadorReproduccion>
with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
bool _reproduciendo = false;
StreamSubscription<EstadoReproduccion>? _estadoSubscription;
@override
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 600))
..addListener(() => setState(() {}));
widget.estadoStream.listen((s) {
_estadoSubscription = widget.estadoStream.listen((s) {
final rep = s == EstadoReproduccion.reproduciendo;
if (rep == _reproduciendo) return;
if (!mounted) return;
setState(() => _reproduciendo = rep);
rep ? _ctrl.repeat(reverse: true) : _ctrl.stop();
});
@@ -206,6 +214,7 @@ class _IndicadorReproduccionState extends State<IndicadorReproduccion>
@override
void dispose() {
_estadoSubscription?.cancel();
_ctrl.dispose();
super.dispose();
}