Files
pluriwave/lib/app.dart
T
FreeTLab d8acf74771
Build & Deploy Pluriwave / Análisis de código (push) Successful in 10s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m19s
feat(ui): implement award mockup redesign
2026-05-20 21:29:47 +02:00

226 lines
7.0 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'estado/estado_radio.dart';
import 'pantallas/pantalla_inicio.dart';
import 'pantallas/pantalla_buscar.dart';
import 'pantallas/pantalla_favoritos.dart';
import 'pantallas/pantalla_ajustes.dart';
import 'tema/pluriwave_theme.dart';
import 'widgets/pluri_glass_surface.dart';
import 'widgets/pluri_icon.dart';
import 'widgets/pluri_wave_scaffold.dart';
import 'package:pluriwave/widgets/mini_reproductor.dart';
class PluriWaveApp extends StatelessWidget {
const PluriWaveApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => EstadoRadio(),
child: MaterialApp(
title: 'PluriWave',
debugShowCheckedModeBanner: false,
theme: PluriWaveTheme.dark(),
darkTheme: PluriWaveTheme.dark(),
themeMode: ThemeMode.dark,
home: const _PaginaPrincipal(),
),
);
}
}
class _PaginaPrincipal extends StatefulWidget {
const _PaginaPrincipal();
@override
State<_PaginaPrincipal> createState() => _PaginaPrincipalState();
}
class _PaginaPrincipalState extends State<_PaginaPrincipal> {
int _indice = 0;
StreamSubscription<String>? _errorSubscription;
EstadoRadio? _estadoSuscrito;
static const _paginas = [
PantallaInicio(),
PantallaBuscar(),
PantallaFavoritos(),
PantallaAjustes(),
];
static const _destinos = [
NavigationDestination(
icon: PluriIcon(glyph: PluriIconGlyph.home),
selectedIcon: PluriIcon(
glyph: PluriIconGlyph.home,
variant: PluriIconVariant.activeGlow,
),
label: 'Inicio',
),
NavigationDestination(
icon: PluriIcon(glyph: PluriIconGlyph.search),
selectedIcon: PluriIcon(
glyph: PluriIconGlyph.search,
variant: PluriIconVariant.activeGlow,
),
label: 'Buscar',
),
NavigationDestination(
icon: PluriIcon(glyph: PluriIconGlyph.favorites),
selectedIcon: PluriIcon(
glyph: PluriIconGlyph.favorites,
variant: PluriIconVariant.activeGlow,
),
label: 'Favoritos',
),
NavigationDestination(
icon: PluriIcon(glyph: PluriIconGlyph.settings),
selectedIcon: PluriIcon(
glyph: PluriIconGlyph.settings,
variant: PluriIconVariant.activeGlow,
),
label: 'Ajustes',
),
];
@override
void didChangeDependencies() {
super.didChangeDependencies();
final estado = context.read<EstadoRadio>();
if (identical(_estadoSuscrito, estado) && _errorSubscription != null) {
return;
}
_errorSubscription?.cancel();
_estadoSuscrito = estado;
_errorSubscription = estado.errorStream.listen((msg) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(msg),
duration: const Duration(seconds: 3),
action: SnackBarAction(label: 'OK', onPressed: () {}),
),
);
});
}
@override
void dispose() {
_errorSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PluriWaveScaffold(
appBar: AppBar(
title: const Text('PluriWave'),
actions: [
IconButton(
icon: const Icon(Icons.bedtime_outlined),
tooltip: 'Timer de sueno',
onPressed: () => _mostrarTimerDialog(context),
),
],
),
body: SafeArea(top: false, child: _paginas[_indice]),
bottomNavigationBar: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const MiniReproductor(),
PluriGlassSurface(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
borderRadius: BorderRadius.circular(999),
child: NavigationBar(
selectedIndex: _indice,
height: 66,
onDestinationSelected: (i) => setState(() => _indice = i),
destinations: _destinos,
),
),
],
),
),
),
);
}
void _mostrarTimerDialog(BuildContext context) {
final estado = context.read<EstadoRadio>();
showModalBottomSheet(
context: context,
builder:
(ctx) => SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Timer de sueño',
style: Theme.of(ctx).textTheme.titleLarge,
),
const SizedBox(height: 16),
if (estado.timer.activo)
StreamBuilder<Duration>(
stream: estado.timer.tiempoRestanteStream,
builder: (ctx, snap) {
final t = snap.data ?? Duration.zero;
final h = t.inHours;
final m = t.inMinutes
.remainder(60)
.toString()
.padLeft(2, '0');
final s = t.inSeconds
.remainder(60)
.toString()
.padLeft(2, '0');
return Column(
children: [
Text(
'${h > 0 ? "${h}h " : ""}${m}m ${s}s',
style: Theme.of(ctx).textTheme.headlineMedium,
),
const SizedBox(height: 8),
FilledButton.tonal(
onPressed: () {
estado.cancelarTimer();
Navigator.pop(ctx);
},
child: const Text('Cancelar timer'),
),
],
);
},
)
else
Wrap(
spacing: 8,
children:
[3, 5, 10, 15, 30, 60, 90, 120, 180]
.map(
(min) => ActionChip(
label: Text('$min min'),
onPressed: () {
estado.iniciarTimer(min);
Navigator.pop(ctx);
},
),
)
.toList(),
),
],
),
),
),
);
}
}