Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com>

Co-authored-by: Azareth-Tr <Azareth-Tr@users.noreply.github.com>
Co-authored-by: eddgranados12 <eddgranados12@users.noreply.github.com>

primeras vistas para el frontend, configuracion para firebase
This commit is contained in:
shinra32
2026-05-22 17:45:54 -06:00
parent ba5e5ea12c
commit 9e6bd04755
26 changed files with 322 additions and 108 deletions

View File

@@ -0,0 +1,495 @@
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
import '../models/models.dart';
import '../widgets/widgets.dart' as w;
class MyHouseScreen extends StatefulWidget {
const MyHouseScreen({super.key});
@override
State<MyHouseScreen> createState() => _MyHouseScreenState();
}
class _MyHouseScreenState extends State<MyHouseScreen> {
HouseModel _casa = const HouseModel(
id: 'casa-01',
calle: 'Av. Insurgentes 245',
colonia: 'Centro',
codigoPostal: '38000',
latitud: 20.5226,
longitud: -100.8191,
radioAlertaMetros: 200,
alertaCercana: true,
alertaMedia: false,
recordatorioDiario: true,
);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.background,
appBar: AppBar(
title: const Text('Mi casa'),
actions: [
IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: () => _mostrarEditarDireccion(context),
tooltip: 'Editar dirección',
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// ── Tarjeta de la casa ──────────────────────────────────────
_CasaCard(casa: _casa),
const SizedBox(height: 16),
// ── Configuración de radio ──────────────────────────────────
w.SectionTitle(title: 'Radio de alerta'),
_RadioAlertaCard(
radioActual: _casa.radioAlertaMetros,
onChanged: (v) => setState(() {
_casa = _casa.copyWith(radioAlertaMetros: v);
}),
),
const SizedBox(height: 16),
// ── Notificaciones ──────────────────────────────────────────
w.SectionTitle(title: 'Notificaciones'),
_NotificacionesCard(
casa: _casa,
onAlertaCercanaChanged: (v) =>
setState(() => _casa = _casa.copyWith(alertaCercana: v)),
onAlertaMediaChanged: (v) =>
setState(() => _casa = _casa.copyWith(alertaMedia: v)),
onRecordatorioChanged: (v) =>
setState(() => _casa = _casa.copyWith(recordatorioDiario: v)),
),
const SizedBox(height: 16),
// ── Horario estimado ────────────────────────────────────────
w.SectionTitle(title: 'Horario del camión'),
_HorarioCard(),
const SizedBox(height: 16),
// ── Agregar otra casa ───────────────────────────────────────
GestureDetector(
onTap: () => _mostrarAgregarCasa(context),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
border: Border.all(
color: AppTheme.primaryMid,
width: 1,
style: BorderStyle.solid),
boxShadow: AppTheme.softShadow,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.add_home_outlined,
color: AppTheme.primary, size: 20),
SizedBox(width: 8),
Text('Agregar otra dirección',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppTheme.primary)),
],
),
),
),
const SizedBox(height: 24),
],
),
);
}
void _mostrarEditarDireccion(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: AppTheme.surface,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(AppTheme.radiusXl)),
),
builder: (_) => _EditarDireccionSheet(casa: _casa),
);
}
void _mostrarAgregarCasa(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Funcionalidad próximamente disponible'),
behavior: SnackBarBehavior.floating,
backgroundColor: AppTheme.primary,
),
);
}
}
// ── Tarjeta principal de la casa ──────────────────────────────────────────────
class _CasaCard extends StatelessWidget {
final HouseModel casa;
const _CasaCard({required this.casa});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
border: Border.all(color: AppTheme.primaryMid, width: 0.8),
boxShadow: AppTheme.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppTheme.primaryLight,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.home_outlined,
color: AppTheme.primary, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(casa.alias,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary)),
const SizedBox(height: 2),
w.StatusBadge.green(
casa.activa ? 'Activa' : 'Inactiva'),
],
),
),
IconButton(
icon: const Icon(Icons.more_vert,
color: AppTheme.textSecondary, size: 20),
onPressed: () {},
),
],
),
const SizedBox(height: 14),
const Divider(color: AppTheme.borderLight),
const SizedBox(height: 10),
// Detalles
_DetailRow(
icon: Icons.location_on_outlined,
text: casa.direccionCompleta,
),
const SizedBox(height: 8),
_DetailRow(
icon: Icons.my_location_outlined,
text:
'${casa.latitud.toStringAsFixed(4)}, ${casa.longitud.toStringAsFixed(4)}',
),
const SizedBox(height: 8),
_DetailRow(
icon: Icons.radar_outlined,
text: 'Alerta a ${casa.radioAlertaMetros} m de distancia',
),
],
),
);
}
}
class _DetailRow extends StatelessWidget {
final IconData icon;
final String text;
const _DetailRow({required this.icon, required this.text});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 15, color: AppTheme.textSecondary),
const SizedBox(width: 8),
Expanded(
child: Text(text,
style: const TextStyle(
fontSize: 13, color: AppTheme.textSecondary, height: 1.4)),
),
],
);
}
}
// ── Radio de alerta ───────────────────────────────────────────────────────────
class _RadioAlertaCard extends StatelessWidget {
final int radioActual;
final ValueChanged<int> onChanged;
const _RadioAlertaCard({required this.radioActual, required this.onChanged});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
border: Border.all(color: AppTheme.border, width: 0.5),
boxShadow: AppTheme.softShadow,
),
child: Column(
children: [200, 400, 600].map((dist) {
final selected = dist == radioActual;
return GestureDetector(
onTap: () => onChanged(dist),
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
decoration: BoxDecoration(
color: selected ? AppTheme.primaryLight : AppTheme.background,
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
border: Border.all(
color: selected ? AppTheme.primary : AppTheme.border,
width: selected ? 1.5 : 0.5,
),
),
child: Row(
children: [
Icon(
selected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: selected ? AppTheme.primary : AppTheme.border,
size: 18,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'$dist metros',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: selected
? AppTheme.primaryDark
: AppTheme.textPrimary,
),
),
),
if (selected)
Text(
dist == 200
? '~2-3 min'
: dist == 400
? '~4-5 min'
: '~6-8 min',
style: const TextStyle(
fontSize: 12,
color: AppTheme.primary,
fontWeight: FontWeight.w500),
),
],
),
),
);
}).toList(),
),
);
}
}
// ── Notificaciones ────────────────────────────────────────────────────────────
class _NotificacionesCard extends StatelessWidget {
final HouseModel casa;
final ValueChanged<bool> onAlertaCercanaChanged;
final ValueChanged<bool> onAlertaMediaChanged;
final ValueChanged<bool> onRecordatorioChanged;
const _NotificacionesCard({
required this.casa,
required this.onAlertaCercanaChanged,
required this.onAlertaMediaChanged,
required this.onRecordatorioChanged,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
border: Border.all(color: AppTheme.border, width: 0.5),
boxShadow: AppTheme.softShadow,
),
child: Column(
children: [
w.LabeledSwitch(
label: 'Alerta cuando el camión esté cerca',
value: casa.alertaCercana,
onChanged: onAlertaCercanaChanged,
),
const Divider(height: 1, color: AppTheme.borderLight),
w.LabeledSwitch(
label: 'Alerta a distancia media',
value: casa.alertaMedia,
onChanged: onAlertaMediaChanged,
),
const Divider(height: 1, color: AppTheme.borderLight),
w.LabeledSwitch(
label: 'Recordatorio diario del horario',
value: casa.recordatorioDiario,
onChanged: onRecordatorioChanged,
),
],
),
);
}
}
// ── Horario del camión ────────────────────────────────────────────────────────
class _HorarioCard extends StatelessWidget {
final List<_HorarioDia> _dias = const [
_HorarioDia(dia: 'Lunes', hora: '8:00 10:00 a.m.', activo: true),
_HorarioDia(dia: 'Martes', hora: '8:00 10:00 a.m.', activo: true),
_HorarioDia(dia: 'Miércoles', hora: 'Sin servicio', activo: false),
_HorarioDia(dia: 'Jueves', hora: '8:00 10:00 a.m.', activo: true),
_HorarioDia(dia: 'Viernes', hora: '8:00 10:00 a.m.', activo: true),
_HorarioDia(dia: 'Sábado', hora: '9:00 11:00 a.m.', activo: true),
_HorarioDia(dia: 'Domingo', hora: 'Sin servicio', activo: false),
];
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
border: Border.all(color: AppTheme.border, width: 0.5),
boxShadow: AppTheme.softShadow,
),
child: Column(
children: _dias.map((d) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 7),
child: Row(
children: [
Text(d.dia,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: d.activo
? AppTheme.textPrimary
: AppTheme.textSecondary)),
const Spacer(),
Text(d.hora,
style: TextStyle(
fontSize: 13,
color: d.activo
? AppTheme.primary
: AppTheme.textSecondary)),
],
),
);
}).toList(),
),
);
}
}
class _HorarioDia {
final String dia;
final String hora;
final bool activo;
const _HorarioDia(
{required this.dia, required this.hora, required this.activo});
}
// ── Sheet de editar dirección ─────────────────────────────────────────────────
class _EditarDireccionSheet extends StatelessWidget {
final HouseModel casa;
const _EditarDireccionSheet({required this.casa});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: 24, right: 24, top: 24,
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Handle
Center(
child: Container(
width: 36, height: 4,
decoration: BoxDecoration(
color: AppTheme.border,
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 20),
const Text('Editar dirección',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w700,
color: AppTheme.textPrimary)),
const SizedBox(height: 20),
w.FormField(
label: 'Calle y número', initialValue: casa.calle),
const SizedBox(height: 14),
Row(
children: [
Expanded(
flex: 3,
child: w.FormField(
label: 'Colonia', initialValue: casa.colonia),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: w.FormField(
label: 'C.P.', initialValue: casa.codigoPostal),
),
],
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Guardar cambios'),
),
),
],
),
);
}
}