- Servicio: nuevo campo esRecurrente + migración DB v2 - Al marcar pagada una factura recurrente se genera la siguiente - Editar facturas existentes desde CrearFacturaScreen - Nuevo método actualizarFactura en FacturaProvider - padding inferior en listas de facturas y servicios (último item visible) - Nota de factura visible en subtítulo - Filtro Vencidas corregido - Monto pendiente total visible en dashboard - Botón undo para revertir factura pagada - Configuración de release signing (keystore) - Pantalla de política de privacidad en la app + PRIVACY_POLICY.md
103 lines
4.6 KiB
Dart
103 lines
4.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../providers/servicio_provider.dart';
|
|
import 'crear_servicio_screen.dart';
|
|
|
|
class ServiciosScreen extends StatelessWidget {
|
|
const ServiciosScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final provider = context.watch<ServicioProvider>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Servicios')),
|
|
body: provider.cargando
|
|
? const Center(child: CircularProgressIndicator())
|
|
: provider.servicios.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.electrical_services, size: 64, color: Colors.grey[400]),
|
|
const SizedBox(height: 16),
|
|
Text('No hay servicios registrados', style: Theme.of(context).textTheme.bodyLarge),
|
|
const SizedBox(height: 8),
|
|
Text('Agrega tu primer servicio', style: Theme.of(context).textTheme.bodyMedium),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
|
|
itemCount: provider.servicios.length,
|
|
itemBuilder: (_, i) {
|
|
final s = provider.servicios[i];
|
|
return TweenAnimationBuilder<double>(
|
|
tween: Tween(begin: 0, end: 1),
|
|
duration: Duration(milliseconds: 300 + i * 60),
|
|
curve: Curves.easeOutCubic,
|
|
builder: (context, value, child) {
|
|
return Opacity(
|
|
opacity: value,
|
|
child: Transform.translate(
|
|
offset: Offset(0, 20 * (1 - value)),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
child: ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: Color(s.color),
|
|
child: Icon(_getIcon(s.icono), color: Colors.white),
|
|
),
|
|
title: Text(s.nombre, style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
subtitle: Text('Vence día ${s.diaVencimiento}'),
|
|
trailing: IconButton(
|
|
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
|
onPressed: () async {
|
|
final ok = await showDialog<bool>(
|
|
context: context,
|
|
builder: (_) => AlertDialog(
|
|
title: const Text('Eliminar servicio'),
|
|
content: Text('¿Eliminar ${s.nombre}? Se eliminarán sus facturas.'),
|
|
actions: [
|
|
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancelar')),
|
|
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Eliminar')),
|
|
],
|
|
),
|
|
);
|
|
if (ok == true) provider.eliminarServicio(s.id!);
|
|
},
|
|
),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => CrearServicioScreen(servicio: s)),
|
|
).then((_) => provider.cargarServicios()),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CrearServicioScreen())).then((_) => provider.cargarServicios()),
|
|
child: const Icon(Icons.add),
|
|
),
|
|
);
|
|
}
|
|
|
|
IconData _getIcon(String name) {
|
|
switch (name) {
|
|
case 'electricidad': return Icons.bolt;
|
|
case 'agua': return Icons.water_drop;
|
|
case 'internet': return Icons.wifi;
|
|
case 'cable': return Icons.tv;
|
|
case 'telefono': return Icons.phone;
|
|
case 'gas': return Icons.local_fire_department;
|
|
case 'alquiler': return Icons.home;
|
|
default: return Icons.receipt;
|
|
}
|
|
}
|
|
}
|