feat(alarm): add musical alarm foundation
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../estado/estado_alarmas.dart';
|
||||
import '../modelos/alarma_musical.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_icon.dart';
|
||||
import '../widgets/pluri_premium_widgets.dart';
|
||||
|
||||
class PantallaAlarmas extends StatelessWidget {
|
||||
const PantallaAlarmas({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.watch<EstadoAlarmas>();
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 124),
|
||||
children: [
|
||||
PluriScreenHeader(
|
||||
title: 'Alarmas musicales',
|
||||
subtitle:
|
||||
'Despertador con radio, vacaciones, aviso previo y fallbacks seguros.',
|
||||
glyph: PluriIconGlyph.alarm,
|
||||
primaryActionLabel: 'Nueva alarma',
|
||||
onPrimaryAction: () => _crearDemo(context),
|
||||
trailing: PluriStatusPill(
|
||||
icon: Icons.alarm_on_rounded,
|
||||
label: '${estado.alarmas.length} alarmas',
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
_DiagnosticoAlarmas(estado: estado),
|
||||
const SizedBox(height: 12),
|
||||
if (estado.alarmas.isEmpty)
|
||||
const _EmptyAlarmas()
|
||||
else
|
||||
for (final alarma in estado.alarmas) ...[
|
||||
_TarjetaAlarma(alarma: alarma),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _crearDemo(BuildContext context) async {
|
||||
final estado = context.read<EstadoAlarmas>();
|
||||
final ahora = TimeOfDay.now();
|
||||
final alarma = estado.servicio.crearAlarma(
|
||||
nombre: 'Despertador musical',
|
||||
hora: ahora.hour,
|
||||
minuto: (ahora.minute + 2) % 60,
|
||||
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||
diasSemana: const [],
|
||||
);
|
||||
await estado.guardarAlarma(alarma);
|
||||
}
|
||||
}
|
||||
|
||||
class _DiagnosticoAlarmas extends StatelessWidget {
|
||||
const _DiagnosticoAlarmas({required this.estado});
|
||||
|
||||
final EstadoAlarmas estado;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final diag = estado.diagnostico;
|
||||
return PluriGlassSurface(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.health_and_safety_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Fiabilidad Android',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
tooltip: 'Revisar',
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
onPressed: estado.cargarDiagnostico,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_EstadoPermiso(
|
||||
label: 'Alarmas exactas',
|
||||
ok: diag?.puedeProgramarExactas ?? false,
|
||||
),
|
||||
_EstadoPermiso(
|
||||
label: 'Notificaciones',
|
||||
ok: diag?.notificacionesPermitidas ?? false,
|
||||
),
|
||||
if (diag == null)
|
||||
Text(
|
||||
'Diagnóstico pendiente. En Android se revisan permisos nativos.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EstadoPermiso extends StatelessWidget {
|
||||
const _EstadoPermiso({required this.label, required this.ok});
|
||||
|
||||
final String label;
|
||||
final bool ok;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Icon(
|
||||
ok ? Icons.check_circle_rounded : Icons.warning_amber_rounded,
|
||||
color: ok ? Colors.greenAccent : Colors.orangeAccent,
|
||||
),
|
||||
title: Text(label),
|
||||
subtitle: Text(ok ? 'Correcto' : 'Requiere revisión'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TarjetaAlarma extends StatelessWidget {
|
||||
const _TarjetaAlarma({required this.alarma});
|
||||
|
||||
final AlarmaMusical alarma;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.read<EstadoAlarmas>();
|
||||
return PluriGlassSurface(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SwitchListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: alarma.activa,
|
||||
onChanged: (value) => estado.cambiarActiva(alarma, value),
|
||||
title: Text(
|
||||
_hora(alarma),
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
subtitle: Text(alarma.nombre),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Chip(label: Text(_programacion(alarma))),
|
||||
Chip(label: Text('Snooze ${alarma.snoozeMinutos} min')),
|
||||
Chip(
|
||||
label: Text(
|
||||
alarma.sonarEnVacaciones
|
||||
? 'Suena en vacaciones'
|
||||
: 'Respeta vacaciones',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (alarma.proximaEjecucion != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text('Próxima: ${alarma.proximaEjecucion!.toLocal()}'),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.skip_next_rounded),
|
||||
label: const Text('Saltar próxima'),
|
||||
onPressed: () => estado.saltarProxima(alarma.id),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
tooltip: 'Eliminar',
|
||||
icon: const Icon(Icons.delete_outline_rounded),
|
||||
onPressed: () => estado.eliminarAlarma(alarma.id),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _hora(AlarmaMusical alarma) =>
|
||||
'${alarma.hora.toString().padLeft(2, '0')}:${alarma.minuto.toString().padLeft(2, '0')}';
|
||||
|
||||
String _programacion(AlarmaMusical alarma) {
|
||||
return switch (alarma.tipoProgramacion) {
|
||||
TipoProgramacionAlarma.unica => 'Una vez',
|
||||
TipoProgramacionAlarma.diaria => 'Diaria',
|
||||
TipoProgramacionAlarma.diasSemana =>
|
||||
'Días: ${alarma.diasSemana.join(', ')}',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyAlarmas extends StatelessWidget {
|
||||
const _EmptyAlarmas();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const PluriGlassSurface(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.alarm_add_rounded, size: 42),
|
||||
SizedBox(height: 12),
|
||||
Text('Todavía no hay alarmas.'),
|
||||
SizedBox(height: 4),
|
||||
Text('Crea una para empezar a diseñar tu despertar musical.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user