feat: agregar pantalla de gestión de direcciones y navegación
This commit is contained in:
@@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<AddressManagerScreen> createState() => _AddressManagerScreenState();
|
||||
}
|
||||
|
||||
class _AddressManagerScreenState extends State<AddressManagerScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _aliasController = TextEditingController();
|
||||
final _streetController = TextEditingController();
|
||||
final _coloniaController = TextEditingController();
|
||||
final Map<String, _RouteInfo> _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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<HomeScreenPlaceholder> {
|
||||
)
|
||||
],
|
||||
),
|
||||
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<HomeScreenPlaceholder> {
|
||||
),
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user