feat(ui): design token discipline, accessibility and i18n pass
- Replace all hardcoded Color literals outside lib/tema with theme tokens (new static brand palette in PluriWaveTokens); media notification uses the brand color instead of the Material default purple - Favorite button on station cards grows to a 48dp target and becomes an independent semantics node for screen readers (Semantics container fix) - All flutter_animate call sites route through the PluriAnimate reduced-motion gate (zero direct .animate() left) - Locale-aware short dates via intl DateFormat (new lib/l10n/formato_fechas.dart) replacing the hardcoded DD/MM/YYYY; proper plural messages for the favorites counter; example stream URL as a localized key - all 13 locales - Rounded shimmer placeholders matching card radii; shimmer loading state in search instead of a bare spinner; rounded icon variants unified in settings; bottom-sheet conventions on the custom station form - Fix latent debug crash: vacation editor read AppLocalizations in initState - 11 new tests (121 total green), flutter analyze clean
This commit is contained in:
@@ -210,7 +210,7 @@ class _SeccionGrabaciones extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.radio_button_checked),
|
||||
const Icon(Icons.radio_button_checked_rounded),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.recordingsSectionTitle,
|
||||
@@ -591,7 +591,7 @@ class _SeccionEcualizador extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.equalizer),
|
||||
const Icon(Icons.equalizer_rounded),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.equalizerTitle,
|
||||
@@ -994,7 +994,7 @@ class _SeccionEmisoras extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.add_circle_outline),
|
||||
const Icon(Icons.add_circle_outline_rounded),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
AppLocalizations.of(context).customStationsTitle,
|
||||
@@ -1002,7 +1002,7 @@ class _SeccionEmisoras extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: Text(AppLocalizations.of(context).customStationsAdd),
|
||||
onPressed: () => _mostrarFormularioAnadir(context),
|
||||
),
|
||||
@@ -1013,14 +1013,18 @@ class _SeccionEmisoras extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).customStationsEmpty,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
for (final emisora in custom)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.radio),
|
||||
leading: const Icon(Icons.radio_rounded),
|
||||
title: Text(
|
||||
localizedStationName(
|
||||
AppLocalizations.of(context),
|
||||
@@ -1036,13 +1040,13 @@ class _SeccionEmisoras extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
icon: const Icon(Icons.play_arrow_rounded),
|
||||
tooltip: AppLocalizations.of(context).playAction,
|
||||
onPressed:
|
||||
() => context.read<EstadoRadio>().reproducir(emisora),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
icon: const Icon(Icons.delete_outline_rounded),
|
||||
tooltip: AppLocalizations.of(context).deleteAction,
|
||||
onPressed:
|
||||
() => context
|
||||
@@ -1061,6 +1065,8 @@ class _SeccionEmisoras extends StatelessWidget {
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
showDragHandle: true,
|
||||
builder: (ctx) => const _FormularioEmisora(),
|
||||
);
|
||||
}
|
||||
@@ -1142,7 +1148,7 @@ class _FormularioEmisoraState extends State<_FormularioEmisora> {
|
||||
controller: _urlCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).streamUrlLabel,
|
||||
hintText: 'http://stream.ejemplo.com:8000/radio',
|
||||
hintText: AppLocalizations.of(context).streamUrlHint,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.url,
|
||||
@@ -1340,7 +1346,7 @@ class _SeccionInfo extends StatelessWidget {
|
||||
builder:
|
||||
(ctx, snap) => ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.favorite_outline),
|
||||
leading: const Icon(Icons.favorite_outline_rounded),
|
||||
title: Text(
|
||||
AppLocalizations.of(ctx).savedFavoritesTitle,
|
||||
),
|
||||
@@ -1366,7 +1372,10 @@ class _SeccionInfo extends StatelessWidget {
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(ctx).stationFilterSubtitle,
|
||||
),
|
||||
trailing: const Icon(Icons.check_circle, color: Colors.green),
|
||||
trailing: Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Theme.of(ctx).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
@@ -1375,7 +1384,10 @@ class _SeccionInfo extends StatelessWidget {
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(ctx).backgroundAudioSubtitle,
|
||||
),
|
||||
trailing: const Icon(Icons.check_circle, color: Colors.green),
|
||||
trailing: Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Theme.of(ctx).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -4,11 +4,14 @@ import 'package:provider/provider.dart';
|
||||
import '../estado/estado_alarmas.dart';
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../l10n/display_names.dart';
|
||||
import '../l10n/formato_fechas.dart';
|
||||
import '../l10n/app_localizations_ext.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/alarma_musical.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../servicios/servicio_programacion_alarmas.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
import '../tema/pluriwave_tokens.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_icon.dart';
|
||||
import '../widgets/pluri_layout.dart';
|
||||
@@ -92,10 +95,14 @@ class _PanelProximaAlarma extends StatelessWidget {
|
||||
final proximaProgramable = proxima?.proximaProgramable;
|
||||
|
||||
return PluriGlassSurface(
|
||||
glowColor: const Color(0xFFFFB86B).withValues(alpha: 0.28),
|
||||
glowColor: context.pluriTokens.warmCoral.withValues(alpha: 0.28),
|
||||
child: Row(
|
||||
children: [
|
||||
const _AssetIcon('assets/icons/alarmas/alarm_music.png', size: 72),
|
||||
_AssetIcon(
|
||||
'assets/icons/alarmas/alarm_music.png',
|
||||
size: 72,
|
||||
semanticLabel: l10n.alarmIconLabel,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -142,15 +149,16 @@ class _TarjetaAlarma extends StatelessWidget {
|
||||
final excepcion = estado.ultimaExcepcionPara(alarma.id);
|
||||
final mensajeVacaciones = _mensajeVacaciones(l10n, estado.vacaciones);
|
||||
return PluriGlassSurface(
|
||||
glowColor: const Color(0xFF22D3EE).withValues(alpha: 0.22),
|
||||
glowColor: context.pluriTokens.electricMagenta.withValues(alpha: 0.22),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const _AssetIcon(
|
||||
_AssetIcon(
|
||||
'assets/icons/alarmas/alarm_music.png',
|
||||
size: 64,
|
||||
semanticLabel: l10n.alarmIconLabel,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
@@ -435,9 +443,10 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const _AssetIcon(
|
||||
_AssetIcon(
|
||||
'assets/icons/alarmas/alarm_music.png',
|
||||
size: 58,
|
||||
semanticLabel: l10n.alarmIconLabel,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
@@ -477,7 +486,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
child: _PickerButton(
|
||||
icon: Icons.event_rounded,
|
||||
label: l10n.dateField,
|
||||
value: _fechaCorta(_fecha),
|
||||
value: _fechaCorta(l10n, _fecha),
|
||||
onTap:
|
||||
_tipo == TipoProgramacionAlarma.unica
|
||||
? _elegirFecha
|
||||
@@ -663,9 +672,10 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
||||
value: _sonarEnVacaciones,
|
||||
onChanged:
|
||||
(value) => setState(() => _sonarEnVacaciones = value),
|
||||
secondary: const _AssetIcon(
|
||||
secondary: _AssetIcon(
|
||||
'assets/icons/alarmas/vacation_wave.png',
|
||||
size: 42,
|
||||
semanticLabel: l10n.vacationIconLabel,
|
||||
),
|
||||
title: Text(l10n.playDuringVacations),
|
||||
subtitle: Text(l10n.playDuringVacationsHint),
|
||||
@@ -1010,15 +1020,16 @@ class _PanelVacaciones extends StatelessWidget {
|
||||
final vacaciones = [...estado.vacaciones]
|
||||
..sort((a, b) => a.inicioDia.compareTo(b.inicioDia));
|
||||
return PluriGlassSurface(
|
||||
glowColor: const Color(0xFF60A5FA).withValues(alpha: 0.22),
|
||||
glowColor: PluriWaveTokens.skyBlue.withValues(alpha: 0.22),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const _AssetIcon(
|
||||
_AssetIcon(
|
||||
'assets/icons/alarmas/vacation_wave.png',
|
||||
size: 48,
|
||||
semanticLabel: l10n.vacationIconLabel,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
@@ -1047,7 +1058,7 @@ class _PanelVacaciones extends StatelessWidget {
|
||||
leading: const Icon(Icons.event_busy_rounded),
|
||||
title: Text(_nombreVisibleVacaciones(l10n, rango)),
|
||||
subtitle: Text(
|
||||
'${_fechaCorta(rango.inicioDia)} → ${_fechaCorta(rango.finDia)}',
|
||||
'${_fechaCorta(l10n, rango.inicioDia)} → ${_fechaCorta(l10n, rango.finDia)}',
|
||||
),
|
||||
trailing: IconButton(
|
||||
tooltip: l10n.deleteRangeTooltip,
|
||||
@@ -1079,7 +1090,9 @@ class _EditorVacacionesSheet extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
late final TextEditingController _nombreController;
|
||||
// Created lazily: AppLocalizations.of(context) cannot be read in
|
||||
// initState (inherited-widget lookup assert in debug builds).
|
||||
TextEditingController? _nombreController;
|
||||
late DateTime _inicio;
|
||||
late DateTime _fin;
|
||||
|
||||
@@ -1089,14 +1102,19 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
final hoy = DateTime.now();
|
||||
_inicio = DateTime(hoy.year, hoy.month, hoy.day);
|
||||
_fin = _inicio.add(const Duration(days: 2));
|
||||
_nombreController = TextEditingController(
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_nombreController ??= TextEditingController(
|
||||
text: AppLocalizations.of(context).vacationsDefaultName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nombreController.dispose();
|
||||
_nombreController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -1131,7 +1149,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
child: _PickerButton(
|
||||
icon: Icons.play_arrow_rounded,
|
||||
label: l10n.startLabel,
|
||||
value: _fechaCorta(_inicio),
|
||||
value: _fechaCorta(l10n, _inicio),
|
||||
onTap: () => _elegirFecha(esInicio: true),
|
||||
),
|
||||
),
|
||||
@@ -1140,7 +1158,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
child: _PickerButton(
|
||||
icon: Icons.stop_rounded,
|
||||
label: l10n.endLabel,
|
||||
value: _fechaCorta(_fin),
|
||||
value: _fechaCorta(l10n, _fin),
|
||||
onTap: () => _elegirFecha(esInicio: false),
|
||||
),
|
||||
),
|
||||
@@ -1183,7 +1201,7 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
final rango = estado.servicio.crearRangoVacaciones(
|
||||
inicio: _inicio,
|
||||
fin: _fin,
|
||||
nombre: _nombreController.text.trim(),
|
||||
nombre: _nombreController?.text.trim() ?? '',
|
||||
);
|
||||
await estado.crearRangoVacaciones(rango);
|
||||
if (mounted) Navigator.pop(context);
|
||||
@@ -1191,11 +1209,15 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
}
|
||||
|
||||
class _AssetIcon extends StatelessWidget {
|
||||
const _AssetIcon(this.asset, {this.size = 44});
|
||||
const _AssetIcon(this.asset, {this.size = 44, this.semanticLabel});
|
||||
|
||||
final String asset;
|
||||
final double size;
|
||||
|
||||
/// S5-R2: meaningful images carry a label; without one the image is
|
||||
/// treated as decorative and excluded from the semantics tree.
|
||||
final String? semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Image.asset(
|
||||
@@ -1203,6 +1225,8 @@ class _AssetIcon extends StatelessWidget {
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.contain,
|
||||
semanticLabel: semanticLabel,
|
||||
excludeFromSemantics: semanticLabel == null,
|
||||
errorBuilder:
|
||||
(_, __, ___) => Icon(Icons.music_note_rounded, size: size * 0.65),
|
||||
);
|
||||
@@ -1301,7 +1325,11 @@ class _EmptyAlarmas extends StatelessWidget {
|
||||
return PluriGlassSurface(
|
||||
child: Column(
|
||||
children: [
|
||||
const _AssetIcon('assets/icons/alarmas/alarm_music.png', size: 92),
|
||||
_AssetIcon(
|
||||
'assets/icons/alarmas/alarm_music.png',
|
||||
size: 92,
|
||||
semanticLabel: l10n.alarmIconLabel,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(l10n.noAlarmsYetTitle),
|
||||
const SizedBox(height: 4),
|
||||
@@ -1326,7 +1354,7 @@ String _hora(AlarmaMusical alarma) =>
|
||||
String _programacion(AppLocalizations l10n, AlarmaMusical alarma) {
|
||||
return switch (alarma.tipoProgramacion) {
|
||||
TipoProgramacionAlarma.unica => l10n.alarmScheduleOnce(
|
||||
_fechaCorta(alarma.fechaUnica ?? DateTime.now()),
|
||||
_fechaCorta(l10n, alarma.fechaUnica ?? DateTime.now()),
|
||||
),
|
||||
TipoProgramacionAlarma.diaria => l10n.dailyOption,
|
||||
TipoProgramacionAlarma.diasSemana => l10n.alarmScheduleWeekdays(
|
||||
@@ -1349,5 +1377,6 @@ String _weekdayShort(AppLocalizations l10n, int day) => switch (day) {
|
||||
_ => '?',
|
||||
};
|
||||
|
||||
String _fechaCorta(DateTime fecha) =>
|
||||
'${fecha.day.toString().padLeft(2, '0')}/${fecha.month.toString().padLeft(2, '0')}/${fecha.year}';
|
||||
// S5-R4: short dates follow the active locale (en-US = M/D/Y, ja = Y/M/D).
|
||||
String _fechaCorta(AppLocalizations l10n, DateTime fecha) =>
|
||||
fechaCortaLocalizada(l10n.localeName, fecha);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../estado/estado_busqueda.dart';
|
||||
import '../tema/pluri_animate.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_icon.dart';
|
||||
@@ -260,9 +260,18 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
Widget _resultados(EstadoBusqueda estado, ThemeData theme) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
if (estado.cargando) {
|
||||
return const SizedBox(
|
||||
height: 220,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
// S5-R6: shimmer placeholders instead of a bare spinner, consistent
|
||||
// with the loading pattern used by the home grid.
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(PluriLayout.horizontal),
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < 4; i++) ...[
|
||||
const TarjetaEmisoraShimmer(esCompacta: true),
|
||||
if (i < 3) const SizedBox(height: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -310,7 +319,11 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
emisora: resultados[i],
|
||||
esCompacta: true,
|
||||
onTap: () => reproducirMinimizado(context, resultados[i]),
|
||||
).animate().fadeIn(delay: (i.clamp(0, 12) * 20).ms).slideY(begin: 0.08);
|
||||
).pluriFadeSlideIn(
|
||||
context,
|
||||
delay: Duration(milliseconds: i.clamp(0, 12) * 20),
|
||||
beginY: 0.08,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,8 @@ class _GrupoFavoritosPanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('${emisoras.length}'),
|
||||
// S5-R5: proper plural message, not a bare number.
|
||||
Text(l10n.stationCount(emisoras.length)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shimmer/shimmer.dart' as shimmer;
|
||||
|
||||
@@ -7,6 +6,7 @@ import '../estado/estado_busqueda.dart';
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../tema/pluri_animate.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_icon.dart';
|
||||
import '../widgets/pluri_layout.dart';
|
||||
@@ -230,7 +230,10 @@ class _PantallaInicioState extends State<PantallaInicio> {
|
||||
),
|
||||
label: Text(e.nombre, maxLines: 1),
|
||||
onPressed: () => reproducirMinimizado(context, e),
|
||||
).animate().fadeIn(delay: (i * 50).ms);
|
||||
).pluriFadeIn(
|
||||
context,
|
||||
delay: Duration(milliseconds: i * 50),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -354,7 +357,11 @@ class _PantallaInicioState extends State<PantallaInicio> {
|
||||
(context, i) => TarjetaEmisora(
|
||||
emisora: emisoras[i],
|
||||
onTap: () => reproducirMinimizado(context, emisoras[i]),
|
||||
).animate().fadeIn(delay: (i * 30).ms).slideY(begin: 0.1),
|
||||
).pluriFadeSlideIn(
|
||||
context,
|
||||
delay: Duration(milliseconds: i * 30),
|
||||
beginY: 0.1,
|
||||
),
|
||||
childCount: emisoras.length,
|
||||
),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
@@ -11,6 +10,7 @@ import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../servicios/servicio_audio.dart';
|
||||
import '../servicios/servicio_timer.dart';
|
||||
import '../tema/pluri_animate.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_wave_scaffold.dart';
|
||||
@@ -129,9 +129,10 @@ class _PantallaReproductorState extends State<PantallaReproductor>
|
||||
_WaveHero(
|
||||
emisora: emisoraActiva,
|
||||
estadoStream: estado.estadoStream,
|
||||
).animate().scale(
|
||||
begin: const Offset(0.86, 0.86),
|
||||
duration: 420.ms,
|
||||
).pluriScaleIn(
|
||||
context,
|
||||
begin: 0.86,
|
||||
duration: const Duration(milliseconds: 420),
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -143,19 +144,24 @@ class _PantallaReproductorState extends State<PantallaReproductor>
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).animate().fadeIn(delay: 150.ms),
|
||||
).pluriFadeIn(context, delay: const Duration(milliseconds: 150)),
|
||||
const SizedBox(height: 10),
|
||||
_InfoChips(
|
||||
emisora: emisoraActiva,
|
||||
).animate().fadeIn(delay: 200.ms).slideY(begin: 0.2),
|
||||
_InfoChips(emisora: emisoraActiva).pluriFadeSlideIn(
|
||||
context,
|
||||
delay: const Duration(milliseconds: 200),
|
||||
beginY: 0.2,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (emisoraActiva.codec != null || emisoraActiva.bitrate != null)
|
||||
Text(
|
||||
_codecInfo(context, emisoraActiva),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white.withValues(alpha: 0.72),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.72),
|
||||
),
|
||||
).animate().fadeIn(delay: 250.ms),
|
||||
).pluriFadeIn(
|
||||
context,
|
||||
delay: const Duration(milliseconds: 250),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
PluriGlassSurface(
|
||||
borderRadius: BorderRadius.circular(tokens.radiusLg),
|
||||
@@ -171,16 +177,25 @@ class _PantallaReproductorState extends State<PantallaReproductor>
|
||||
color: tokens.warmCoral,
|
||||
altura: 46,
|
||||
),
|
||||
).animate().fadeIn(delay: 280.ms),
|
||||
).pluriFadeIn(context, delay: const Duration(milliseconds: 280)),
|
||||
const Spacer(),
|
||||
_Controles(
|
||||
estado: estado,
|
||||
emisora: emisoraActiva,
|
||||
).animate().fadeIn(delay: 300.ms).slideY(begin: 0.3),
|
||||
).pluriFadeSlideIn(
|
||||
context,
|
||||
delay: const Duration(milliseconds: 300),
|
||||
beginY: 0.3,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const _GrabacionWidget().animate().fadeIn(delay: 360.ms),
|
||||
const _GrabacionWidget().pluriFadeIn(
|
||||
context,
|
||||
delay: const Duration(milliseconds: 360),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
_TimerWidget(estado: estado).animate().fadeIn(delay: 400.ms),
|
||||
_TimerWidget(
|
||||
estado: estado,
|
||||
).pluriFadeIn(context, delay: const Duration(milliseconds: 400)),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -247,9 +262,7 @@ class _WaveHero extends StatelessWidget {
|
||||
height: size + 12,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.16),
|
||||
),
|
||||
border: Border.all(color: t.glassBorder),
|
||||
),
|
||||
),
|
||||
PluriGlassSurface(
|
||||
@@ -275,9 +288,9 @@ class _WaveHero extends StatelessWidget {
|
||||
if (cargando)
|
||||
Container(
|
||||
color: Colors.black45,
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -288,7 +301,9 @@ class _WaveHero extends StatelessWidget {
|
||||
child: Icon(
|
||||
Icons.wifi_off_rounded,
|
||||
size: 56,
|
||||
color: Colors.white.withValues(alpha: 0.85),
|
||||
color: theme.colorScheme.onSurface.withValues(
|
||||
alpha: 0.85,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -706,7 +721,7 @@ class _Controles extends StatelessWidget {
|
||||
minWidth: 56,
|
||||
minHeight: 56,
|
||||
),
|
||||
color: Colors.white.withValues(alpha: 0.78),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.78),
|
||||
tooltip: l10n.stopAction,
|
||||
onPressed: cargando ? null : estado.detenerReproduccion,
|
||||
),
|
||||
@@ -753,12 +768,12 @@ class _Controles extends StatelessWidget {
|
||||
child: Center(
|
||||
child:
|
||||
cargando
|
||||
? const SizedBox(
|
||||
? SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: Colors.white,
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
|
||||
Reference in New Issue
Block a user