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> implementacion de login, vistas, correcion de errores en vista registro, domicilios
This commit is contained in:
15
recolecta_app/lib/features/home/citizen_home_screen.dart
Normal file
15
recolecta_app/lib/features/home/citizen_home_screen.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CitizenHomeScreen extends StatelessWidget {
|
||||
const CitizenHomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Inicio')),
|
||||
body: const Center(
|
||||
child: Text('TODO: Citizen Home Screen - Mostrar tarjeta ETA'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
recolecta_app/lib/features/home/citizen_shell.dart
Normal file
50
recolecta_app/lib/features/home/citizen_shell.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CitizenShell extends StatefulWidget {
|
||||
const CitizenShell({super.key, required this.child});
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<CitizenShell> createState() => _CitizenShellState();
|
||||
}
|
||||
|
||||
class _CitizenShellState extends State<CitizenShell> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
void _onTap(int index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/home');
|
||||
break;
|
||||
case 1:
|
||||
context.go('/guide');
|
||||
break;
|
||||
case 2:
|
||||
context.go('/feedback');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: widget.child,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: _currentIndex,
|
||||
onTap: _onTap,
|
||||
items: const [
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Inicio'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.menu_book), label: 'Guía'),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.feedback),
|
||||
label: 'Retroalimentación',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
recolecta_app/lib/features/home/colonias_data.dart
Normal file
14
recolecta_app/lib/features/home/colonias_data.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
// Coordenadas de referencia para el centro de cada colonia en Celaya, Gto.
|
||||
// Para el MVP, estas coordenadas son fijas y coinciden con el JSON de `colonias-rutas`.
|
||||
// En una versión futura, podrían venir de una API de geocodificación o de la BD.
|
||||
const Map<String, LatLng> kColoniasCoordinates = {
|
||||
'Zona Centro': LatLng(20.52254, -100.81153),
|
||||
'Las Arboledas': LatLng(20.51422, -100.82793),
|
||||
'San Juanico': LatLng(20.54066, -100.83831),
|
||||
'Los Olivos': LatLng(20.54621, -100.77274),
|
||||
'Rancho Seco': LatLng(20.49110, -100.81080),
|
||||
'Las Insurgentes': LatLng(20.52427, -100.79548),
|
||||
'Trojes': LatLng(20.50899, -100.77167),
|
||||
};
|
||||
580
recolecta_app/lib/features/home/house_screen.dart
Normal file
580
recolecta_app/lib/features/home/house_screen.dart
Normal file
@@ -0,0 +1,580 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/models/ui_models.dart';
|
||||
import 'colonias_data.dart';
|
||||
import '../../core/widgets/app_widgets.dart';
|
||||
|
||||
class MyHouseScreen extends StatefulWidget {
|
||||
const MyHouseScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MyHouseScreen> createState() => _MyHouseScreenState();
|
||||
}
|
||||
|
||||
class _MyHouseScreenState extends State<MyHouseScreen> {
|
||||
bool _isLoading = true;
|
||||
UIHouseModel? _casa;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cargarDomicilio();
|
||||
}
|
||||
|
||||
Future<void> _cargarDomicilio() async {
|
||||
try {
|
||||
const storage = FlutterSecureStorage();
|
||||
final token = await storage.read(key: 'token') ?? '';
|
||||
|
||||
if (token.isEmpty) {
|
||||
setState(() => _isLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
final dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: const String.fromEnvironment(
|
||||
'API_BASE_URL',
|
||||
defaultValue: 'http://localhost:8000',
|
||||
),
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
),
|
||||
);
|
||||
|
||||
final res = await dio.get('/addresses');
|
||||
if (res.data is List && (res.data as List).isNotEmpty) {
|
||||
final addr = res.data[0];
|
||||
setState(() {
|
||||
_casa = UIHouseModel.fromJson(addr);
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
debugPrint('Error al cargar domicilio: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Scaffold(
|
||||
backgroundColor: AppTheme.background,
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (_casa == null) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.background,
|
||||
appBar: AppBar(title: const Text('Mi casa')),
|
||||
body: const Center(child: Text('No tienes un domicilio registrado.')),
|
||||
);
|
||||
}
|
||||
|
||||
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: [
|
||||
_CasaCard(casa: _casa!),
|
||||
const SizedBox(height: 16),
|
||||
const AppSectionTitle(title: 'Mapa del Sector (Restringido)'),
|
||||
_MapaColoniaRestringido(colonia: _casa!.colonia),
|
||||
const SizedBox(height: 16),
|
||||
const AppSectionTitle(title: 'Radio de alerta'),
|
||||
_RadioAlertaCard(
|
||||
radioActual: _casa!.radioAlertaMetros,
|
||||
onChanged: (v) =>
|
||||
setState(() => _casa = _casa!.copyWith(radioAlertaMetros: v)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const AppSectionTitle(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),
|
||||
const AppSectionTitle(title: 'Horario del camión'),
|
||||
_HorarioCard(),
|
||||
const SizedBox(height: 16),
|
||||
GestureDetector(
|
||||
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Funcionalidad próximamente disponible'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: AppTheme.primary,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
|
||||
border: Border.all(color: AppTheme.primaryMid),
|
||||
boxShadow: AppTheme.softShadow,
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
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!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tarjeta de la casa ────────────────────────────────────────────────────────
|
||||
class _CasaCard extends StatelessWidget {
|
||||
final UIHouseModel 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: [
|
||||
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: 4),
|
||||
AppStatusBadge.green(casa.activa ? 'Activa' : 'Inactiva'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Divider(color: AppTheme.borderLight),
|
||||
const SizedBox(height: 10),
|
||||
_DetailRow(
|
||||
icon: Icons.location_on_outlined,
|
||||
text: casa.direccionCompleta,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_DetailRow(
|
||||
icon: Icons.radar_outlined,
|
||||
text: 'Alerta a ${casa.radioAlertaMetros} m de distancia',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Mapa de Colonia (Restringido para Privacidad) ──────────────────────────────
|
||||
class _MapaColoniaRestringido extends StatelessWidget {
|
||||
final String colonia;
|
||||
const _MapaColoniaRestringido({required this.colonia});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Usa las coordenadas del archivo centralizado de datos de colonias.
|
||||
final center =
|
||||
kColoniasCoordinates[colonia] ?? const LatLng(20.5222, -100.8123);
|
||||
|
||||
// Creamos una "caja" o límite geográfico de aprox 1km a la redonda
|
||||
final bounds = LatLngBounds(
|
||||
LatLng(center.latitude - 0.01, center.longitude - 0.01),
|
||||
LatLng(center.latitude + 0.01, center.longitude + 0.01),
|
||||
);
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
|
||||
border: Border.all(color: AppTheme.border, width: 1),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: FlutterMap(
|
||||
options: MapOptions(
|
||||
initialCameraFit: CameraFit.bounds(bounds: bounds),
|
||||
// ESTO ES LA MAGIA DE LA PRIVACIDAD: Bloquea el mapa a esta caja
|
||||
cameraConstraint: CameraConstraint.contain(bounds: bounds),
|
||||
interactionOptions: const InteractionOptions(
|
||||
flags: InteractiveFlag.drag | InteractiveFlag.pinchZoom,
|
||||
),
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'com.onlineshack.recolecta',
|
||||
),
|
||||
CircleLayer(
|
||||
circles: [
|
||||
CircleMarker(
|
||||
point: center,
|
||||
color: AppTheme.primary.withValues(alpha: 0.2),
|
||||
borderColor: AppTheme.primary,
|
||||
borderStrokeWidth: 2,
|
||||
radius: 350, // 350 metros a la redonda remarcados
|
||||
useRadiusInMeter: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 UIHouseModel 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: [
|
||||
AppLabeledSwitch(
|
||||
label: 'Alerta cuando el camión esté cerca',
|
||||
value: casa.alertaCercana,
|
||||
onChanged: onAlertaCercanaChanged,
|
||||
),
|
||||
const Divider(height: 1, color: AppTheme.borderLight),
|
||||
AppLabeledSwitch(
|
||||
label: 'Alerta a distancia media',
|
||||
value: casa.alertaMedia,
|
||||
onChanged: onAlertaMediaChanged,
|
||||
),
|
||||
const Divider(height: 1, color: AppTheme.borderLight),
|
||||
AppLabeledSwitch(
|
||||
label: 'Recordatorio diario del horario',
|
||||
value: casa.recordatorioDiario,
|
||||
onChanged: onRecordatorioChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Horario ───────────────────────────────────────────────────────────────────
|
||||
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 editar dirección ────────────────────────────────────────────────────
|
||||
class _EditarDireccionSheet extends StatelessWidget {
|
||||
final UIHouseModel 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: [
|
||||
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),
|
||||
AppFormField(label: 'Calle y número', initialValue: casa.calle),
|
||||
const SizedBox(height: 14),
|
||||
AppFormField(label: 'Colonia', initialValue: casa.colonia),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Guardar cambios'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
35
recolecta_app/lib/features/home/main_shell.dart
Normal file
35
recolecta_app/lib/features/home/main_shell.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/widgets/app_widgets.dart';
|
||||
import '../eta/eta_screen.dart';
|
||||
import '../alerts/alerts_screen.dart';
|
||||
import 'house_screen.dart';
|
||||
import '../profile/profile_screen.dart';
|
||||
|
||||
class MainShell extends StatefulWidget {
|
||||
const MainShell({super.key});
|
||||
|
||||
@override
|
||||
State<MainShell> createState() => _MainShellState();
|
||||
}
|
||||
|
||||
class _MainShellState extends State<MainShell> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
static const List<Widget> _screens = [
|
||||
EtaScreen(),
|
||||
AlertsScreen(),
|
||||
MyHouseScreen(),
|
||||
ProfileScreen(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: IndexedStack(index: _currentIndex, children: _screens),
|
||||
bottomNavigationBar: AppBottomNav(
|
||||
currentIndex: _currentIndex,
|
||||
onTap: (i) => setState(() => _currentIndex = i),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user