From 211e14cef599e4277c24d994a651ca9e1f36dcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20M=C3=A9ndez?= Date: Sat, 23 May 2026 09:17:36 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20pantalla=20de=20gesti=C3=B3n?= =?UTF-8?q?=20de=20direcciones=20y=20navegaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/router/app_router.dart | 6 + .../screens/address_manager_screen.dart | 309 ++++++++++++++++++ .../screens/home_screen_placeholder.dart | 37 +++ 3 files changed, 352 insertions(+) create mode 100644 lib/features/auth/presentation/screens/address_manager_screen.dart diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 8e87c7f..abf0ef5 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../features/auth/presentation/bloc/auth_bloc.dart'; import '../../features/auth/presentation/bloc/auth_state.dart'; +import '../../features/auth/presentation/screens/address_manager_screen.dart'; import '../../features/auth/presentation/screens/login_screen.dart'; import '../../features/auth/presentation/screens/register_screen.dart'; import '../../features/auth/presentation/screens/home_screen_placeholder.dart'; @@ -14,6 +15,7 @@ abstract final class AppRoutes { static const String login = '/login'; static const String register = '/register'; static const String home = '/home'; + static const String addresses = '/addresses'; } /// Configuración central de navegación con go_router. @@ -72,6 +74,10 @@ GoRouter createRouter(AuthBloc authBloc) { path: AppRoutes.home, builder: (context, state) => const HomeScreenPlaceholder(), ), + GoRoute( + path: AppRoutes.addresses, + builder: (context, state) => const AddressManagerScreen(), + ), ], ); } diff --git a/lib/features/auth/presentation/screens/address_manager_screen.dart b/lib/features/auth/presentation/screens/address_manager_screen.dart new file mode 100644 index 0000000..2d49b8e --- /dev/null +++ b/lib/features/auth/presentation/screens/address_manager_screen.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../../core/router/app_router.dart'; + +class AddressManagerScreen extends StatefulWidget { + const AddressManagerScreen({super.key}); + + @override + State createState() => _AddressManagerScreenState(); +} + +class _AddressManagerScreenState extends State { + final _formKey = GlobalKey(); + final _aliasController = TextEditingController(); + final _streetController = TextEditingController(); + final _coloniaController = TextEditingController(); + final Map _routeCatalog = { + 'zona centro': _RouteInfo(routeId: 'RUTA-01', schedule: 'Matutino (06:30 - 07:15)'), + 'las arboledas': _RouteInfo(routeId: 'RUTA-01', schedule: 'Matutino (07:00 - 07:30)'), + 'trojes': _RouteInfo(routeId: 'RUTA-13', schedule: 'Matutino (06:40 - 07:10)'), + 'san juanico': _RouteInfo(routeId: 'RUTA-03', schedule: 'Matutino (06:45 - 07:15)'), + 'los olivos': _RouteInfo(routeId: 'RUTA-04', schedule: 'Matutino (07:00 - 07:40)'), + 'rancho seco': _RouteInfo(routeId: 'RUTA-05', schedule: 'Vespertino (14:15 - 15:00)'), + 'las insurgentes': _RouteInfo(routeId: 'RUTA-12', schedule: 'Matutino (06:35 - 07:10)'), + }; + + final List<_SavedAddress> _savedAddresses = [ + _SavedAddress( + alias: 'Casa', + street: 'Av. Siempre Viva 123', + colonia: 'Zona Centro', + routeId: 'RUTA-01', + schedule: 'Matutino (06:30 - 07:15)', + ), + ]; + + @override + void dispose() { + _aliasController.dispose(); + _streetController.dispose(); + _coloniaController.dispose(); + super.dispose(); + } + + void _saveAddress() { + if (!_formKey.currentState!.validate()) { + return; + } + + final colonia = _coloniaController.text.trim(); + final routeInfo = _routeCatalog[colonia.toLowerCase()]; + + setState(() { + _savedAddresses.insert( + 0, + _SavedAddress( + alias: _aliasController.text.trim(), + street: _streetController.text.trim(), + colonia: colonia, + routeId: routeInfo?.routeId ?? 'Ruta no encontrada', + schedule: routeInfo?.schedule ?? 'Horario no disponible', + ), + ); + _aliasController.clear(); + _streetController.clear(); + _coloniaController.clear(); + }); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Dirección guardada correctamente')), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF5F7F5), + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + title: const Text( + 'Mis direcciones', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + ), + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.black), + onPressed: () => context.go(AppRoutes.home), + ), + ), + bottomNavigationBar: NavigationBar( + selectedIndex: 1, + onDestinationSelected: (index) { + if (index == 0) { + context.go(AppRoutes.home); + } + }, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.home_outlined), + selectedIcon: Icon(Icons.home), + label: 'Inicio', + ), + NavigationDestination( + icon: Icon(Icons.location_on_outlined), + selectedIcon: Icon(Icons.location_on), + label: 'Direcciones', + ), + ], + ), + body: ListView( + padding: const EdgeInsets.all(20), + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF1B5E20), Color(0xFF2E7D32)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(24), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.place, color: Colors.white, size: 34), + SizedBox(height: 12), + Text( + 'Agrega tus direcciones', + style: TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + 'Guarda uno o varios domicilios para relacionarlos con tus rutas de recolección.', + style: TextStyle(color: Colors.white70, fontSize: 14, height: 1.4), + ), + ], + ), + ), + const SizedBox(height: 20), + Card( + elevation: 1, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(18), + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: _aliasController, + decoration: const InputDecoration( + labelText: 'Alias', + prefixIcon: Icon(Icons.badge_outlined), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Escribe un alias'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _streetController, + decoration: const InputDecoration( + labelText: 'Calle y número', + prefixIcon: Icon(Icons.home_outlined), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Escribe la dirección'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _coloniaController, + decoration: const InputDecoration( + labelText: 'Colonia', + prefixIcon: Icon(Icons.map_outlined), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Escribe la colonia'; + } + return null; + }, + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: _saveAddress, + icon: const Icon(Icons.add_location_alt, color: Colors.white), + label: const Text('Guardar dirección'), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2E7D32), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 20), + Text( + 'Mis direcciones', + style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ..._savedAddresses.map( + (address) => Card( + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: ListTile( + leading: const CircleAvatar( + backgroundColor: Color(0xFFE8F5E9), + child: Icon(Icons.route, color: Color(0xFF2E7D32)), + ), + title: Text(address.alias, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Padding( + padding: const EdgeInsets.only(top: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(address.street), + const SizedBox(height: 4), + Text(address.colonia), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _InfoChip(label: address.routeId, icon: Icons.local_shipping), + _InfoChip(label: address.schedule, icon: Icons.access_time), + ], + ), + ], + ), + ), + isThreeLine: true, + ), + ), + ), + ], + ), + ); + } +} + +class _SavedAddress { + final String alias; + final String street; + final String colonia; + final String routeId; + final String schedule; + + _SavedAddress({ + required this.alias, + required this.street, + required this.colonia, + required this.routeId, + required this.schedule, + }); +} + +class _RouteInfo { + final String routeId; + final String schedule; + + const _RouteInfo({required this.routeId, required this.schedule}); +} + +class _InfoChip extends StatelessWidget { + final String label; + final IconData icon; + + const _InfoChip({required this.label, required this.icon}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFFF1F8E9), + borderRadius: BorderRadius.circular(999), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14, color: const Color(0xFF2E7D32)), + const SizedBox(width: 6), + Text( + label, + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), + ), + ], + ), + ); + } +} diff --git a/lib/features/auth/presentation/screens/home_screen_placeholder.dart b/lib/features/auth/presentation/screens/home_screen_placeholder.dart index 7dfce03..88da210 100644 --- a/lib/features/auth/presentation/screens/home_screen_placeholder.dart +++ b/lib/features/auth/presentation/screens/home_screen_placeholder.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import '../../../../core/router/app_router.dart'; + class HomeScreenPlaceholder extends StatefulWidget { const HomeScreenPlaceholder({super.key}); @@ -203,6 +205,26 @@ class _HomeScreenPlaceholderState extends State { ) ], ), + bottomNavigationBar: NavigationBar( + selectedIndex: 0, + onDestinationSelected: (index) { + if (index == 1) { + context.go(AppRoutes.addresses); + } + }, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.home_outlined), + selectedIcon: Icon(Icons.home), + label: 'Inicio', + ), + NavigationDestination( + icon: Icon(Icons.location_on_outlined), + selectedIcon: Icon(Icons.location_on), + label: 'Direcciones', + ), + ], + ), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10), child: Column( @@ -240,6 +262,21 @@ class _HomeScreenPlaceholderState extends State { ), const SizedBox(height: 16), + Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: ListTile( + leading: const CircleAvatar( + backgroundColor: Color(0xFFE8F5E9), + child: Icon(Icons.add_location_alt, color: Color(0xFF2E7D32)), + ), + title: const Text('Agregar direcciones'), + subtitle: const Text('Abre el panel para registrar tus domicilios y vincularlos a tu colonia.'), + trailing: const Icon(Icons.chevron_right), + onTap: () => context.go(AppRoutes.addresses), + ), + ), + const SizedBox(height: 16), + // --- 2. RELOJ DE TIEMPO ESTIMADO INTERACTIVO --- Card( color: Colors.white,