3 Commits

Author SHA1 Message Date
imlildud
8df86daf25 feat: notification state save in localstorage 2026-05-22 21:24:00 -06:00
imlildud
7cf5f13d88 feat: adress save in localstorage 2026-05-22 21:00:22 -06:00
imlildud
04d56e18c6 feat: register adress logic 2026-05-22 20:32:22 -06:00
3 changed files with 600 additions and 40 deletions

View File

@@ -0,0 +1,48 @@
import 'dart:convert';
class Domicilio {
final String nombre;
final String colonia;
final String calle;
final String numero;
final String id;
Domicilio({
required this.nombre,
required this.colonia,
required this.calle,
required this.numero,
required this.id,
});
String get direccionCompleta => '$colonia, $calle $numero';
Map<String, dynamic> toJson() => {
'nombre': nombre,
'colonia': colonia,
'calle': calle,
'numero': numero,
'id': id,
};
factory Domicilio.fromJson(Map<String, dynamic> json) {
return Domicilio(
nombre: json['nombre'],
colonia: json['colonia'],
calle: json['calle'],
numero: json['numero'],
id: json['id'],
);
}
static String encode(List<Domicilio> domicilios) {
return json.encode(
domicilios.map((d) => d.toJson()).toList(),
);
}
static List<Domicilio> decode(String domiciliosString) {
final List<dynamic> data = json.decode(domiciliosString);
return data.map((item) => Domicilio.fromJson(item)).toList();
}
}

View File

@@ -1,9 +1,142 @@
// configuracion.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'rutas.dart'; import 'rutas.dart';
class ConfiguracionView extends StatelessWidget { class ConfiguracionView extends StatefulWidget {
const ConfiguracionView({super.key}); const ConfiguracionView({super.key});
@override
State<ConfiguracionView> createState() => _ConfiguracionViewState();
}
class _ConfiguracionViewState extends State<ConfiguracionView> {
String selectedOption = '7 días'; // Valor por defecto
bool _isLoading = true;
// Opciones del combobox
final List<String> opciones = [
'Cada día',
'Cada 3 días',
'Cada semana',
'Cada quincena',
];
// Mapa para mostrar valores más amigables
final Map<String, String> opcionesMap = {
'Cada día': '1 día',
'Cada 3 días': '3 días',
'Cada semana': '7 días',
'Cada quincena': '15 días',
};
@override
void initState() {
super.initState();
_cargarPreferencia();
}
// Cargar la preferencia guardada
Future<void> _cargarPreferencia() async {
try {
final prefs = await SharedPreferences.getInstance();
final String? savedOption = prefs.getString('notificacion_frecuencia');
setState(() {
if (savedOption != null && opciones.contains(savedOption)) {
selectedOption = savedOption;
}
_isLoading = false;
});
} catch (e) {
print('Error al cargar preferencia: $e');
setState(() {
_isLoading = false;
});
}
}
// Guardar la preferencia
Future<void> _guardarPreferencia(String value) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('notificacion_frecuencia', value);
// Mostrar mensaje de confirmación
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Notificaciones: $value'),
backgroundColor: colorAzul,
duration: const Duration(seconds: 1),
),
);
}
} catch (e) {
print('Error al guardar preferencia: $e');
}
}
// Mostrar diálogo con opciones
void _mostrarSelector() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Frecuencia de notificaciones',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorAzul,
),
),
const SizedBox(height: 20),
...opciones.map((opcion) {
return ListTile(
leading: Icon(
selectedOption == opcion ? Icons.radio_button_checked : Icons.radio_button_unchecked,
color: colorAzul,
),
title: Text(
opcion,
style: TextStyle(
fontSize: 18,
fontWeight: selectedOption == opcion ? FontWeight.bold : FontWeight.normal,
color: selectedOption == opcion ? colorAzul : Colors.black87,
),
),
trailing: Text(
opcionesMap[opcion]!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
onTap: () {
setState(() {
selectedOption = opcion;
});
_guardarPreferencia(opcion);
Navigator.pop(context);
},
);
}),
const SizedBox(height: 10),
],
),
);
},
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@@ -11,6 +144,7 @@ class ConfiguracionView extends StatelessWidget {
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
// AppBar personalizado
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
@@ -31,31 +165,68 @@ class ConfiguracionView extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
// Contenido
Expanded( Expanded(
child: Padding( child: _isLoading
? const Center(
child: CircularProgressIndicator(
color: colorAzul,
),
)
: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
children: [ children: [
// Selector de notificaciones
GestureDetector( GestureDetector(
onTap: () {}, onTap: _mostrarSelector,
child: Container( child: Container(
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(15),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 4), border: Border.all(color: Colors.black, width: 4),
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
), ),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.notifications_active_outlined, size: 60), const Icon(Icons.notifications_active_outlined, size: 60),
SizedBox(width: 10), const SizedBox(width: 10),
Text('7 días', Text(
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), selectedOption,
Spacer(), style: const TextStyle(
Icon(Icons.keyboard_arrow_down, size: 50), fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
const Icon(Icons.keyboard_arrow_down, size: 50),
], ],
), ),
), ),
), ),
const SizedBox(height: 20),
// Información adicional
Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: colorAzul.withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
child: Row(
children: [
Icon(Icons.info_outline, color: colorAzul, size: 30),
const SizedBox(width: 10),
Expanded(
child: Text(
'Recibirás notificaciones cada ${opcionesMap[selectedOption]}',
style: TextStyle(
fontSize: 16,
color: colorAzul,
),
),
),
],
),
),
], ],
), ),
), ),

View File

@@ -1,14 +1,302 @@
// domicilios.dart // domicilios.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'rutas.dart'; import 'rutas.dart';
import '../models/domicilio_model.dart';
import 'dart:math';
class DomiciliosView extends StatelessWidget { class DomiciliosView extends StatefulWidget {
const DomiciliosView({super.key}); const DomiciliosView({super.key});
@override
State<DomiciliosView> createState() => _DomiciliosViewState();
}
class _DomiciliosViewState extends State<DomiciliosView> {
List<Domicilio> domicilios = [];
bool _isLoading = true;
// Controladores para el formulario
final TextEditingController nombreController = TextEditingController();
final TextEditingController coloniaController = TextEditingController();
final TextEditingController calleController = TextEditingController();
final TextEditingController numeroController = TextEditingController();
@override
void initState() {
super.initState();
_cargarDomicilios();
}
@override
void dispose() {
nombreController.dispose();
coloniaController.dispose();
calleController.dispose();
numeroController.dispose();
super.dispose();
}
// Cargar domicilios guardados
Future<void> _cargarDomicilios() async {
try {
final prefs = await SharedPreferences.getInstance();
final String? domiciliosString = prefs.getString('domicilios');
if (domiciliosString != null && domiciliosString.isNotEmpty) {
setState(() {
domicilios = Domicilio.decode(domiciliosString);
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
}
} catch (e) {
print('Error al cargar domicilios: $e');
setState(() {
_isLoading = false;
});
}
}
// Guardar domicilios en SharedPreferences
Future<void> _guardarDomicilios() async {
try {
final prefs = await SharedPreferences.getInstance();
final String domiciliosString = Domicilio.encode(domicilios);
await prefs.setString('domicilios', domiciliosString);
} catch (e) {
print('Error al guardar domicilios: $e');
}
}
void _mostrarDialogoAgregar() {
// Limpiar controladores
nombreController.clear();
coloniaController.clear();
calleController.clear();
numeroController.clear();
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Título
const Text(
'Añadir domicilio',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: colorAzul,
),
),
const SizedBox(height: 20),
// Campo: Nombre del domicilio
_buildCampoTexto(
controller: nombreController,
hint: 'Nombre del domicilio',
icon: Icons.home_outlined,
),
const SizedBox(height: 15),
// Campo: Colonia
_buildCampoTexto(
controller: coloniaController,
hint: 'Colonia',
icon: Icons.location_city_outlined,
),
const SizedBox(height: 15),
// Campo: Calle
_buildCampoTexto(
controller: calleController,
hint: 'Calle',
icon: Icons.streetview,
),
const SizedBox(height: 15),
// Campo: Número
_buildCampoTexto(
controller: numeroController,
hint: 'Número',
icon: Icons.numbers,
keyboardType: TextInputType.number,
),
const SizedBox(height: 25),
// Botones
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Botón Cancelar
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: colorAzul, width: 2),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'Cancelar',
style: TextStyle(fontSize: 16, color: colorAzul),
),
),
),
const SizedBox(width: 15),
// Botón Agregar
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: colorAzul,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
onPressed: () {
_agregarDomicilio();
},
child: const Text(
'Agregar',
style: TextStyle(fontSize: 16, color: Colors.white),
),
),
),
],
),
],
),
),
);
},
);
}
Widget _buildCampoTexto({
required TextEditingController controller,
required String hint,
required IconData icon,
TextInputType keyboardType = TextInputType.text,
}) {
return TextField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hint,
prefixIcon: Icon(icon, color: colorAzul),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: const BorderSide(color: colorAzul),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: BorderSide(color: colorAzul.withOpacity(0.5)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: const BorderSide(color: colorAzul, width: 2),
),
),
);
}
void _agregarDomicilio() async {
// Validar que todos los campos estén llenos
if (nombreController.text.isEmpty ||
coloniaController.text.isEmpty ||
calleController.text.isEmpty ||
numeroController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Por favor, llena todos los campos'),
backgroundColor: Colors.red,
),
);
return;
}
// Crear nuevo domicilio con ID único
final nuevoDomicilio = Domicilio(
nombre: nombreController.text,
colonia: coloniaController.text,
calle: calleController.text,
numero: numeroController.text,
id: DateTime.now().millisecondsSinceEpoch.toString(), // ID único
);
// Agregar a la lista
setState(() {
domicilios.add(nuevoDomicilio);
});
// Guardar en SharedPreferences
await _guardarDomicilios();
// Cerrar el diálogo
Navigator.pop(context);
// Mostrar mensaje de éxito
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Domicilio "${nombreController.text}" agregado'),
backgroundColor: colorAzul,
duration: const Duration(seconds: 2),
),
);
}
void _eliminarDomicilio(int index) async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Eliminar domicilio'),
content: Text('¿Deseas eliminar "${domicilios[index].nombre}"?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancelar'),
),
TextButton(
onPressed: () async {
setState(() {
domicilios.removeAt(index);
});
// Guardar cambios en SharedPreferences
await _guardarDomicilios();
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Domicilio eliminado'),
backgroundColor: Colors.red,
duration: Duration(seconds: 2),
),
);
},
child: const Text('Eliminar', style: TextStyle(color: Colors.red)),
),
],
);
},
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Colors.white, // Fondo blanco para el contenido color: Colors.white,
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -35,11 +323,49 @@ class DomiciliosView extends StatelessWidget {
), ),
// Contenido // Contenido
Expanded( Expanded(
child: Padding( child: _isLoading
padding: const EdgeInsets.all(20.0), ? const Center(
child: CircularProgressIndicator(
color: colorAzul,
),
)
: domicilios.isEmpty
? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( Icon(
Icons.home_outlined,
size: 100,
color: Colors.grey.withOpacity(0.5),
),
const SizedBox(height: 20),
Text(
'No hay domicilios agregados',
style: TextStyle(
fontSize: 18,
color: Colors.grey.withOpacity(0.7),
),
),
const SizedBox(height: 10),
Text(
'Toca el botón + para agregar',
style: TextStyle(
fontSize: 14,
color: Colors.grey.withOpacity(0.5),
),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: domicilios.length,
itemBuilder: (context, index) {
final domicilio = domicilios[index];
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Container(
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(15),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 4), border: Border.all(color: Colors.black, width: 4),
@@ -49,37 +375,52 @@ class DomiciliosView extends StatelessWidget {
children: [ children: [
const Icon(Icons.home_outlined, size: 60), const Icon(Icons.home_outlined, size: 60),
const SizedBox(width: 10), const SizedBox(width: 10),
const Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text('Mi Casa', children: [
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), Text(
Text('Centro, Ruta 1', domicilio.nombre,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: const TextStyle(
], fontSize: 22,
fontWeight: FontWeight.bold,
),
),
Text(
domicilio.direccionCompleta,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
), ),
const Spacer(),
IconButton( IconButton(
onPressed: () {}, onPressed: () => _eliminarDomicilio(index),
icon: const Icon(Icons.more_horiz, size: 40), icon: const Icon(Icons.delete_outline, size: 40),
color: Colors.red,
), ),
], ],
), ),
), ),
const SizedBox(height: 20), );
GestureDetector( },
onTap: () {}, ),
child: Container( ),
width: double.infinity, // Botón flotante de agregar
height: 100, Padding(
decoration: BoxDecoration( padding: const EdgeInsets.all(20),
color: colorAzul, child: GestureDetector(
borderRadius: BorderRadius.circular(20), onTap: _mostrarDialogoAgregar,
), child: Container(
child: const Icon(Icons.add, color: Colors.white, size: 80), width: double.infinity,
), height: 100,
), decoration: BoxDecoration(
], color: colorAzul,
borderRadius: BorderRadius.circular(20),
),
child: const Icon(Icons.add, color: Colors.white, size: 80),
), ),
), ),
), ),