From f1ae9a301f5a0e1292850129448d6391cd6b6d86 Mon Sep 17 00:00:00 2001 From: shinra32 Date: Fri, 22 May 2026 16:08:48 -0600 Subject: [PATCH] Co-authored-by: Azareth-Tr Co-authored-by: MENDOZA BALLARDO GAEL RICARDO configuracion para firebase --- claude (1).md | 366 ++++++++++++++++ recolecta_app/lib/app/app.dart | 276 ++++++++++++ recolecta_app/lib/firebase_options.dart | 14 + recolecta_app/lib/main.dart | 85 +--- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 8 + recolecta_app/pubspec.lock | 399 +++++++++++++++++- recolecta_app/pubspec.yaml | 6 + .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 3 + 11 files changed, 1086 insertions(+), 83 deletions(-) create mode 100644 claude (1).md create mode 100644 recolecta_app/lib/app/app.dart create mode 100644 recolecta_app/lib/firebase_options.dart diff --git a/claude (1).md b/claude (1).md new file mode 100644 index 0000000..2ccf286 --- /dev/null +++ b/claude (1).md @@ -0,0 +1,366 @@ +# Contexto del Proyecto — Sistema de Recolección Inteligente y Privada de Residuos + +> **Documento para compartir con asistentes de IA (Claude, ChatGPT, etc.)** +> Última actualización: Mayo 2026 +> Equipo: 4 personas, hackathon, stack principal Flutter + FastAPI + +--- + +## 1. ¿Qué estamos construyendo? + +Una **app móvil (Flutter)** que notifica a los ciudadanos cuándo llegará el camión recolector a su domicilio **sin revelar la ubicación del camión en tiempo real ni mostrar el mapa completo de la ruta**. Incluye educación sobre separación de residuos mediante una mascota de IA y gestión de cambios operativos (fallas mecánicas, reasignaciones de rutas). Tres roles: ciudadano, chofer, admin. + +### El problema que resuelve +La gente no sabe cuándo pasará el camión → saca la basura demasiado temprano/tarde → problemas de salud pública. La solución obvia (GPS en vivo del camión) es un riesgo de seguridad y uso inadecuado. Nuestra app balancea información útil con privacidad operativa. + +### Alcance del MVP +- **7 colonias** de Celaya, Gto. mapeadas a **6 rutas** (datos provistos en JSON). +- Notificaciones push en 3 momentos: ruta inicia, camión cerca (~15 min), servicio finalizado. +- Validación de domicilio con OCR de recibo (luz/agua) que se borra tras extraer la dirección. +- Mascota IA interactiva + guía offline de separación de residuos. +- Feedback ciudadano + quiz post-recolección. +- Panel admin con mapa (única vista que ve coordenadas) y gestión de reasignaciones. + +--- + +## 2. Reglas innegociables (Privacidad por Diseño) + +Estas son las **prohibiciones del reto** convertidas en requisitos de código. Cualquier feature que las viole se rechaza: + +1. **NUNCA se devuelven coordenadas del camión al ciudadano.** Solo texto: `"Llega en ~15 min"` / `"7:20–7:35 p.m."`. +2. **NUNCA hay un mapa con el camión moviéndose** en la vista ciudadana (prohibido el *route tracker*). +3. **Visión de túnel:** el ciudadano solo ve su ruta asignada. Cero visibilidad de colonias vecinas u otros usuarios (anti-*snooping*). +4. La ubicación del camión **solo existe en el panel admin** (para troubleshooting). +5. **Mensajería preventiva:** textos que desalientan sacar basura fuera de horario o perseguir la unidad. +6. Quejas hacia la unidad **no exponen** la identidad del chofer (solo "unidad 101"; el admin sí ve quién la conduce). +7. Los recibos (validación de domicilio) **no se almacenan crudos**: se procesan, se valida, se guarda solo `verified=true` y se **borra** la imagen. + +**Implementación técnica del túnel:** +- Backend: Row Level Security (RLS) de Supabase — el ciudadano físicamente no puede leer `route_positions` ni domicilios ajenos aunque evada la API. +- Frontend: notificaciones FCM por **topic de ruta** (`topic_RUTA-01`) — cada ciudadano se suscribe solo al topic de su `routeId`. + +--- + +## 3. Stack tecnológico (y por qué) + +| Capa | Tecnología | Razón | +|------|------------|-------| +| **Frontend móvil** | Flutter + Riverpod (estado) + `dio` (HTTP) + `go_router` | Multiplataforma iOS/Android con un solo código. Riverpod evita polling excesivo. | +| **Backend / API** | Python + FastAPI | REST async, rápida de escribir, separa lógica de negocio de datos. | +| **Base de datos** | PostgreSQL (vía Supabase); PostGIS opcional | El mapeo colonia→ruta es directo (JSON), así que no necesitamos point-in-polygon. PostGIS solo si quieren validar por polígono en el futuro. | +| **Auth + RBAC** | Supabase Auth (JWT) + Row Level Security (RLS) | RLS aplica el túnel **a nivel de BD**, no solo en código — aunque alguien evada la API, la BD niega el acceso. | +| **Almacenamiento** | Supabase Storage | Para subir recibo temporalmente. Se borra tras validación. | +| **Simulación** | APScheduler (FastAPI cron job) | Avanza `positionId` de cada ruta (1→8) y dispara notificaciones. No hace falta cálculo geoespacial. | +| **Push** | Firebase Cloud Messaging (FCM) | Asíncrono, ligero, integración nativa con Flutter. | +| **Mascota IA** | API de Claude/Gemini con system prompt sobre separación | El chat educativo. Guía estática (JSON local) funciona offline. | +| **OCR validación** | Tesseract o modelo de visión en backend | Extrae dirección del recibo, compara, descarta imagen. | +| **Deploy** | Cloud Run / Render (backend scale-to-zero) + Supabase | Escala a cero en valles de inactividad. | + +--- + +## 4. Datos provistos (la base real del proyecto) + +Nos entregaron 3 archivos JSON que **definen el modelo y simplifican la implementación**: + +### `rutas.json` (15 rutas, usamos 6) +- Cada ruta tiene un `routeId` (ej. `"RUTA-01"`), `truckId` (101–115), y **8 posiciones fijas** (`positionId` 1→8) con lat/lng, velocidad y timestamp. +- Todas salen y regresan al **Relleno Sanitario** (`20.5111, -100.9037`) — posición 1 = posición 8. +- Velocidad = 0 en posición 5 (punto más lejano / vuelta). +- Estas coordenadas **solo se usan en el panel admin** — nunca se mandan al ciudadano. + +### `notificaciones.json` (motor de eventos) +Define los 3 eventos que **NO se calculan por distancia**, sino por **cambio de `positionId`**: + +| Evento | Se dispara cuando | Mensaje (resumen) | +|--------|-------------------|-------------------| +| `ROUTE_START` | `positionId` pasa de 1 → 2 | Salió del relleno rumbo a tu sector | +| `TRUCK_PROXIMITY` | `positionId` llega a 4 | A menos de 15 min; saca tus bolsas | +| `ROUTE_COMPLETED` | `positionId` llega a 8 | Servicio del día finalizado | + +### `colonias-rutas.json` (el puente colonia → ruta) +Mapea cada colonia a un `routeId` + horario. **Esto es el "túnel":** ciudadano → colonia → `routeId` → solo eventos de ESA ruta. + +**Alcance del MVP (7 colonias / 6 rutas):** + +| Colonia | routeId | Turno | +|---------|---------|-------| +| Zona Centro | RUTA-01 | Matutino | +| Las Arboledas | RUTA-01 | Matutino | +| San Juanico | RUTA-03 | Matutino | +| Los Olivos | RUTA-04 | Matutino | +| **Rancho Seco** | **RUTA-05** | **Vespertino** ← única vespertina; úsenla para demostrar reasignación | +| Las Insurgentes | RUTA-12 | Matutino | +| Trojes | RUTA-13 | Matutino | + +En el registro, el ciudadano **elige su colonia de un dropdown** (no geocodificación). Eso le asigna su `routeId` y resuelve el túnel sin PostGIS. + +--- + +## 5. Modelo de datos (jerarquía: Usuario → Domicilio → Colonia → Ruta) + +```sql +users (id, email/phone, role: 'citizen'|'driver'|'admin') +addresses (id, user_id, label, calle, colonia, route_id, + verified BOOL, verified_method, verified_at) +colonias (id, nombre, route_id, horario_estimado, turno) +routes (id 'RUTA-01'..'RUTA-13', name, truck_id, + turno: 'matutino'|'vespertino', + status: 'pendiente'|'en_ruta'|'completada'|'diferida'|'reasignada', + current_position_id INT) -- el "avance" simulado vive aquí (1-8) +route_positions (route_id, position_id, lat, lng, speed, ts) + -- de rutas.json; ⚠️ SOLO panel admin (RLS) +units (id 101.., plate, status) -- 1:1 con driver y con route +drivers (id, user_id, unit_id) -- cada chofer = 1 unidad +collection_events(id, route_id, address_id, status, collected_at) +feedback (id, user_id, address_id, type, target_unit_id, + message, rating, created_at) -- target = unidad, NO chofer +notifications (id, user_id, route_id, type, payload, sent_at) +route_changes (id, route_id, from_unit, to_unit, reason, created_at) +``` + +**Cambio clave:** el estado del camión NO es una coordenada calculada en vivo, sino el campo `current_position_id` (1–8). Las coordenadas de `route_positions` solo las consume el panel admin. + +--- + +## 6. Cómo funciona la simulación (el corazón del sistema) + +La simulación NO calcula distancias ni ETA por geolocalización. Es un **índice que avanza** + disparadores de eventos. + +### El cron job (APScheduler en FastAPI) +```python +# El "estado" de cada ruta es solo en qué positionId va (1-8). +estado = { "RUTA-01": 1, "RUTA-03": 1, "RUTA-04": 1, + "RUTA-05": 1, "RUTA-12": 1, "RUTA-13": 1 } + +def tick(): # corre cada N segundos (APScheduler) + for route_id, pos in estado.items(): + if pos < 8: + nueva = pos + 1 + estado[route_id] = nueva # = routes.current_position_id en BD + disparar_si_aplica(route_id, pos, nueva) + +def disparar_si_aplica(route_id, antes, ahora): + # Lee notificaciones.json y dispara según el cambio de positionId + if antes == 1 and ahora == 2: push(route_id, "ROUTE_START") + elif ahora == 4: push(route_id, "TRUCK_PROXIMITY") + elif ahora == 8: push(route_id, "ROUTE_COMPLETED") +``` + +### El túnel vía FCM topics +```python +# La push solo va a quienes están en esa ruta (no broadcast). +def push(route_id, evento): + payload = NOTIFICACIONES[evento]["pushPayload"] # de notificaciones.json + fcm.send_to_topic(f"topic_{route_id}", payload) # ej. "topic_RUTA-01" +# El ciudadano se suscribe SOLO al topic de su routeId → nunca recibe de otra ruta. +# El payload solo lleva texto (title/body); JAMÁS lat/lng. +``` + +### ETA sin coordenadas (endpoint ciudadano) +```python +# GET /eta?address_id=123 (ciudadano) +route = get_route_for_address(address_id) # derivado de colonia +pos = route.current_position_id + +if pos < 4: mensaje = "El camión va en camino a tu sector" +elif pos == 4: mensaje = "Llega en aproximadamente 15 minutos" +elif pos < 8: mensaje = "Está atendiendo tu zona; saca tus bolsas" +else: mensaje = "Servicio del día finalizado" + +# Devuelve SOLO texto: +return {"mensaje": mensaje, "status": route.status} +# NUNCA coordenadas. +``` + +### RLS (el túnel a nivel de BD) +```sql +-- El ciudadano SOLO ve sus domicilios (refuerzo del túnel): +CREATE POLICY citizen_own_addresses ON addresses + FOR SELECT USING (auth.uid() = user_id); + +-- route_positions (coordenadas): sin policy para citizen = sin acceso. Solo admin: +CREATE POLICY admin_route_positions ON route_positions + FOR SELECT USING ( + (SELECT role FROM users WHERE id = auth.uid()) = 'admin' + ); +``` + +--- + +## 7. Lógica de turnos y reasignación + +De las notas del equipo: +- Una ruta es **matutina O vespertina**, nunca ambas. +- Ruta **matutina** que no se completó → se **difiere** al día siguiente + **notifica**. +- Ruta **vespertina** que la unidad no puede atender → la **reemplaza una unidad de mañana** (ya libre) + **notifica**. (Rancho Seco / RUTA-05 es el caso de demo.) +- Cada cambio escribe en `route_changes` y dispara FCM con evento `reasignacion`/`retraso` al topic de la ruta. + +--- + +## 8. Reparto del equipo (4 personas) + +| Persona | Bloque | Responsabilidad | +|---------|--------|-----------------| +| **P1 (Líder)** | Setup + Backend & Seguridad | Inicializar Flutter/backend, arquitectura API REST, JWT, RBAC/RLS, schema, validación de domicilio (OCR + privacidad). | +| **P2** | Simulación + Notificaciones + Datos | Carga de los 3 JSON, simulación por `positionId` (cron), motor de eventos/FCM, lógica de turnos/reasignación, endpoint de ETA por texto. | +| **P3** | Frontend Ciudadano (Flutter) | Registro, alta/validación de domicilios (dropdown colonia), vista ETA, suscripción FCM al topic de su ruta, buzón de retroalimentación, quiz de satisfacción. | +| **P4** | Frontend Admin + Chofer + Mascota IA | Paneles admin (mapa de posiciones, única vista con coordenadas) y chofer (marcar avance, reportar falla), mascota interactiva (LLM), guía de separación offline (JSON local). | + +**Pair programming:** VS Code Live Share. Parejas sugeridas: P1↔P2 (backend/datos) y P3↔P4 (Flutter). + +--- + +## 9. Estructura de archivos del monorepo + +``` +ONLINESHACK/ <- raíz del repo (git aquí) +├── .gitignore <- raíz (cubre Flutter + Python) +├── README.md +├── PLAN.md <- el checklist del proyecto +├── claude.md <- este archivo +├── recolecta_app/ <- Flutter +│ ├── lib/ +│ │ ├── core/ (theme, network/dio_client, auth, constants) +│ │ ├── features/ (auth, addresses, eta, notifications, +│ │ │ separation_guide, feedback, admin, driver) +│ │ └── shared/ (widgets reutilizables) +│ ├── assets/ +│ │ └── .env <- (opcional) config pública: API_BASE_URL, anon key +│ ├── pubspec.yaml +│ └── ... +└── backend/ <- FastAPI + ├── app/ + │ ├── api/ (routers: auth, addresses, routes, eta, feedback, admin) + │ ├── core/ (config, security/jwt, deps) + │ ├── schemas/ (DTOs Pydantic) + │ ├── services/ (simulation, notifications, reassignment, ocr) + │ ├── data/ <- los 3 JSON provistos + │ │ ├── colonias-rutas.json + │ │ ├── notificaciones.json + │ │ └── rutas.json + │ └── db/ (session, seed.py) + ├── secrets/ + │ └── firebase-adminsdk.json <- ignorado + ├── .env <- secretos reales (ignorado) + ├── .env.example <- plantilla (versionada) + ├── requirements.txt + └── main.py +``` + +--- + +## 10. Secretos y `.env` + +**Backend `.env` (secretos reales, nunca al cliente):** +```bash +SUPABASE_URL=https://xxx.supabase.co +SUPABASE_ANON_KEY=eyJ... # llave pública; puede ir al cliente +SUPABASE_SERVICE_ROLE_KEY=eyJ... # llave secreta; SOLO backend +JWT_SECRET=xxx +FIREBASE_CREDENTIALS_PATH=./secrets/firebase-adminsdk.json +SIMULATION_TICK_SECONDS=10 +``` + +**Flutter `.env` (SOLO config pública, viaja en el .apk/.ipa):** +```bash +API_BASE_URL=http://10.0.2.2:8000 # 10.0.2.2 = localhost desde emulador Android +SUPABASE_URL=https://xxx.supabase.co +SUPABASE_ANON_KEY=eyJ... # llave anon: pública por diseño +``` + +**Principio:** cualquier cosa que meta en Flutter es extraíble → no es secreto. Los secretos reales (service_role, admin SDK) viven únicamente en el backend. + +--- + +## 11. Decisiones arquitectónicas clave (y por qué) + +1. **Supabase RLS en vez de RBAC solo en código:** aunque alguien evada la API (bug, exploit), la BD niega el acceso. Es "defense in depth" para Privacidad por Diseño. + +2. **Simulación por `positionId` en vez de cálculo geoespacial:** los datos provistos ya definen 8 posiciones fijas con timestamps. Avanzar un índice es trivial, y los eventos salen de `notificaciones.json` sin cálculos. Evita complejidad innecesaria. + +3. **FCM por topic de ruta en vez de push individual:** escala mejor (un mensaje llega a todos los ciudadanos de la ruta). El túnel se resuelve en la suscripción: cada quien solo está en `topic_{su_routeId}`. + +4. **Dropdown de colonia en vez de geocodificación:** los datos ya mapean colonia→ruta. Geocodificar sería redundante y costaría llamadas a API. El dropdown es más rápido, más barato y suficiente para el MVP. + +5. **OCR + borrado del recibo:** cumple "Privacidad por Diseño" literalmente — la imagen nunca persiste. Solo queda `verified=true` + método + timestamp. + +6. **Guía offline + chat IA:** la guía estática (JSON embebido en Flutter) garantiza que funcione sin red. El chat con LLM es la capa extra cuando hay conexión. + +--- + +## 12. Endpoints clave del backend + +### Ciudadano (autenticado, rol `citizen`) +- `POST /auth/register` — registro con email/teléfono +- `POST /auth/login` — devuelve JWT +- `GET /colonias` — lista para el dropdown (nombre + horario) +- `POST /addresses` — dar de alta domicilio (calle + colonia elegida → deriva `route_id`) +- `POST /addresses/{id}/verify` — sube recibo, OCR extrae dirección, compara, borra imagen, guarda `verified=true` +- `GET /eta?address_id=X` — devuelve `{mensaje, status}` SIN coordenadas +- `POST /feedback` — queja/rating hacia la unidad (NO chofer) + +### Chofer (rol `driver`) +- `GET /routes/mine` — su ruta/unidad asignada +- `POST /collections/{route_id}` — marcar recolección en un domicilio +- `POST /incidents` — reportar falla mecánica (dispara reasignación) + +### Admin (rol `admin`) +- `GET /routes` — todas las rutas + `current_position_id` +- `GET /routes/{id}/positions` — las 8 coordenadas de `route_positions` (⚠️ única vista con lat/lng) +- `POST /routes/{id}/reassign` — reasignar ruta a otra unidad + dispara FCM +- `GET /drivers` — lista de choferes + su unidad + +--- + +## 13. Checklist de validación de privacidad (antes de cada demo) + +Antes de mostrar cualquier feature, revisar: +- [ ] ¿El endpoint del ciudadano devuelve coordenadas? → ❌ rechazado +- [ ] ¿Hay un mapa con el camión moviéndose en vista ciudadana? → ❌ rechazado +- [ ] ¿El ciudadano puede pedir datos de otra ruta u otro usuario? → ❌ debe fallar (RLS) +- [ ] ¿Las quejas exponen el nombre/ID del chofer? → ❌ solo `target_unit_id` +- [ ] ¿El recibo del ciudadano quedó almacenado en BD o storage? → ❌ debe estar borrado +- [ ] ¿Los mensajes desalientan sacar basura fuera de horario? → ✅ validar textos + +--- + +## 14. Comandos útiles de arranque + +### Flutter +```bash +cd recolecta_app +flutter pub get +flutter run # Android: conectar emulador o dispositivo +``` + +### Backend +```bash +cd backend +python -m venv .venv && source .venv/bin/activate # (Windows: .venv\Scripts\activate) +pip install -r requirements.txt +# Crear .env copiando .env.example y llenando valores +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +### Simulación (una vez el backend esté corriendo) +El cron de APScheduler arranca automáticamente con el backend si está configurado. Para forzar un tick manual desde consola Python: +```python +from app.services.simulation import tick +tick() # avanza current_position_id de cada ruta y dispara eventos +``` + +--- + +## 15. Para la demo (guion de 5–7 min) + +1. **Registro + validación de domicilio** (mostrar que el recibo se borra). +2. **Correr la simulación acelerada** (tick cada 5 segundos): llega `ROUTE_START` → `TRUCK_PROXIMITY` → `ROUTE_COMPLETED`. Resaltar: **sin mapa, sin ubicación**, solo texto. +3. **Mascota IA** enseñando a separar residuos + guía offline. +4. **Falla de unidad en Rancho Seco (RUTA-05 vespertina)** → reasignación a unidad matutina → notificación al ciudadano. +5. **Panel admin** con el mapa de las 8 posiciones (contraste: solo admin ve coordenadas). +6. **Quiz post-recolección** + queja a la unidad (sin exponer chofer). +7. **Diapositiva de arquitectura** + cómo cada decisión cumple Privacidad por Diseño. + +--- + +**Fin del contexto. Este documento debe ir al repo como `claude.md` para que el equipo lo comparta con sus asistentes de IA.** diff --git a/recolecta_app/lib/app/app.dart b/recolecta_app/lib/app/app.dart new file mode 100644 index 0000000..3a38672 --- /dev/null +++ b/recolecta_app/lib/app/app.dart @@ -0,0 +1,276 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import '../firebase_options.dart'; + +final bootstrapProvider = FutureProvider((ref) async { + await dotenv.load(fileName: 'assets/.env'); + + // Inicializar Firebase (si hay DefaultFirebaseOptions, úsalas; sino, intenta initializeApp() y espera que haya google-services/Info.plist) + final FirebaseOptions? options = DefaultFirebaseOptions.currentPlatform; + if (options != null) { + await Firebase.initializeApp(options: options); + } else { + await Firebase.initializeApp(); + } + + // Registrar handler para mensajes en background + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); +}); + +// Handler top-level requerido por FCM +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // Asegurar Firebase inicializado en background isolate + try { + await Firebase.initializeApp(); + } catch (_) { + // ignore if already initialized + } + // Aquí puedes procesar y guardar la notificación si hace falta + debugPrint( + 'FCM background message received: ${message.messageId} | data: ${message.data}', + ); +} + +final apiClientProvider = Provider((ref) { + final baseUrl = dotenv.env['API_BASE_URL'] ?? 'http://10.0.2.2:8000'; + + return Dio( + BaseOptions( + baseUrl: baseUrl, + connectTimeout: const Duration(seconds: 15), + receiveTimeout: const Duration(seconds: 15), + headers: const {'Content-Type': 'application/json'}, + ), + ); +}); + +final secureStorageProvider = Provider((ref) { + return const FlutterSecureStorage(); +}); + +final routerProvider = Provider((ref) { + return GoRouter( + initialLocation: '/home', + routes: [ + GoRoute( + path: '/home', + name: 'home', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/status', + name: 'status', + builder: (context, state) => const StatusPage(), + ), + ], + ); +}); + +class RecolectaApp extends ConsumerWidget { + const RecolectaApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bootstrap = ref.watch(bootstrapProvider); + + return bootstrap.when( + loading: () => const MaterialApp( + debugShowCheckedModeBanner: false, + home: BootstrapLoadingPage(), + ), + error: (error, stackTrace) => MaterialApp( + debugShowCheckedModeBanner: false, + home: BootstrapErrorPage(error: error), + ), + data: (_) => MaterialApp.router( + debugShowCheckedModeBanner: false, + title: 'Recolecta', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1F6F78)), + scaffoldBackgroundColor: const Color(0xFFF4F7F6), + useMaterial3: true, + ), + routerConfig: ref.watch(routerProvider), + ), + ); + } +} + +class BootstrapLoadingPage extends StatelessWidget { + const BootstrapLoadingPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } +} + +class BootstrapErrorPage extends StatelessWidget { + const BootstrapErrorPage({super.key, required this.error}); + + final Object error; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, size: 48), + const SizedBox(height: 16), + const Text( + 'No se pudo cargar la configuración inicial.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + error.toString(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ), + ); + } +} + +class HomePage extends ConsumerWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final dio = ref.read(apiClientProvider); + final storage = ref.read(secureStorageProvider); + final baseUrl = dio.options.baseUrl; + + return Scaffold( + appBar: AppBar( + title: const Text('Recolecta'), + actions: [ + IconButton( + onPressed: () => context.goNamed('status'), + icon: const Icon(Icons.route), + tooltip: 'Estado', + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: ListView( + children: [ + const Text( + 'Bootstrap listo', + style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700), + ), + const SizedBox(height: 8), + Text( + 'La app ya carga .env, Riverpod y GoRouter para la base del MVP.', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 24), + _InfoCard(title: 'API base URL', value: baseUrl, icon: Icons.cloud), + const SizedBox(height: 16), + _InfoCard( + title: 'Secure Storage', + value: storage.runtimeType.toString(), + icon: Icons.lock, + ), + const SizedBox(height: 16), + _ImageCard( + title: 'Widget listo para caché de imágenes', + imageUrl: + 'https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&w=800&q=80', + ), + ], + ), + ), + ); + } +} + +class StatusPage extends StatelessWidget { + const StatusPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Estado')), + body: const Padding( + padding: EdgeInsets.all(24), + child: Text( + 'Aquí después se conectarán ETA, notificaciones, ruta asignada y métricas de privacidad.', + ), + ), + ); + } +} + +class _InfoCard extends StatelessWidget { + const _InfoCard({ + required this.title, + required this.value, + required this.icon, + }); + + final String title; + final String value; + final IconData icon; + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + child: ListTile( + leading: Icon(icon), + title: Text(title), + subtitle: Text(value), + ), + ); + } +} + +class _ImageCard extends StatelessWidget { + const _ImageCard({required this.title, required this.imageUrl}); + + final String title; + final String imageUrl; + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + imageUrl: imageUrl, + height: 180, + width: double.infinity, + fit: BoxFit.cover, + placeholder: (context, url) => const SizedBox( + height: 180, + child: Center(child: CircularProgressIndicator()), + ), + errorWidget: (context, url, error) => const SizedBox( + height: 180, + child: Center(child: Icon(Icons.image_not_supported)), + ), + ), + Padding(padding: const EdgeInsets.all(16), child: Text(title)), + ], + ), + ); + } +} diff --git a/recolecta_app/lib/firebase_options.dart b/recolecta_app/lib/firebase_options.dart new file mode 100644 index 0000000..54c0d9f --- /dev/null +++ b/recolecta_app/lib/firebase_options.dart @@ -0,0 +1,14 @@ +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + +// Placeholder file for Firebase configuration. +// Run `flutterfire configure` to generate a proper `firebase_options.dart` file +// and replace the implementation below. For local dev you can also rely on +// `google-services.json` (Android) and `GoogleService-Info.plist` (iOS) instead +// of providing explicit FirebaseOptions. + +class DefaultFirebaseOptions { + static FirebaseOptions? get currentPlatform { + // TODO: Replace with generated FirebaseOptions from `flutterfire configure`. + return null; + } +} diff --git a/recolecta_app/lib/main.dart b/recolecta_app/lib/main.dart index d23eeb2..79706f0 100644 --- a/recolecta_app/lib/main.dart +++ b/recolecta_app/lib/main.dart @@ -1,88 +1,9 @@ -// ============================================================ -// main.dart — App Recolecta (corregido) -// Cambios aplicados: -// 1. Agregados imports faltantes (material, riverpod) -// 2. Corregido ColorScheme.fromSeed (faltaba el tipo) -// 3. Corregido MainAxisAlignment.center (faltaba el tipo) -// 4. Agregado WidgetsFlutterBinding.ensureInitialized() antes de dotenv -// ============================================================ - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; + +import 'app/app.dart'; Future main() async { - // NECESARIO antes de cualquier async en main (inicializa el motor de Flutter) WidgetsFlutterBinding.ensureInitialized(); - - // Carga el .env desde assets/ - await dotenv.load(fileName: "assets/.env"); - - runApp(const ProviderScope(child: MyApp())); + runApp(const ProviderScope(child: RecolectaApp())); } - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Recolecta', - theme: ThemeData( - // ❌ ANTES: colorScheme: .fromSeed(seedColor: Colors.deepPurple), - // ✅ AHORA: faltaba el tipo ColorScheme antes del .fromSeed - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Recolecta - Demo'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - ), - body: Center( - child: Column( - // ❌ ANTES: mainAxisAlignment: .center, - // ✅ AHORA: faltaba el tipo MainAxisAlignment antes del .center - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} \ No newline at end of file diff --git a/recolecta_app/linux/flutter/generated_plugin_registrant.cc b/recolecta_app/linux/flutter/generated_plugin_registrant.cc index e71a16d..d0e7f79 100644 --- a/recolecta_app/linux/flutter/generated_plugin_registrant.cc +++ b/recolecta_app/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); } diff --git a/recolecta_app/linux/flutter/generated_plugins.cmake b/recolecta_app/linux/flutter/generated_plugins.cmake index 2e1de87..ce58916 100644 --- a/recolecta_app/linux/flutter/generated_plugins.cmake +++ b/recolecta_app/linux/flutter/generated_plugins.cmake @@ -3,9 +3,11 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/recolecta_app/macos/Flutter/GeneratedPluginRegistrant.swift b/recolecta_app/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..7397616 100644 --- a/recolecta_app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/recolecta_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,14 @@ import FlutterMacOS import Foundation +import firebase_core +import firebase_messaging +import flutter_secure_storage_macos +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/recolecta_app/pubspec.lock b/recolecta_app/pubspec.lock index 767246e..d3dfddc 100644 --- a/recolecta_app/pubspec.lock +++ b/recolecta_app/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "99.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.dev" + source: hosted + version: "1.3.59" analyzer: dependency: transitive description: @@ -41,6 +49,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -65,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f + url: "https://pub.dev" + source: hosted + version: "1.1.0" collection: dependency: transitive description: @@ -105,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" fake_async: dependency: transitive description: @@ -113,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" file: dependency: transitive description: @@ -121,11 +185,75 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.dev" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.dev" + source: hosted + version: "2.24.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + url: "https://pub.dev" + source: hosted + version: "15.2.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + url: "https://pub.dev" + source: hosted + version: "4.6.10" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + url: "https://pub.dev" + source: hosted + version: "3.10.10" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_dotenv: dependency: "direct main" description: @@ -150,11 +278,64 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -171,6 +352,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + url: "https://pub.dev" + source: hosted + version: "14.8.1" + hooks: + dependency: transitive + description: + name: hooks + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -195,6 +400,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -275,6 +504,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -291,6 +536,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" pool: dependency: transitive description: @@ -307,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -315,6 +632,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" shelf: dependency: transitive description: @@ -376,6 +701,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" + url: "https://pub.dev" + source: hosted + version: "2.4.2+1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.dev" + source: hosted + version: "2.4.2+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -408,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" + url: "https://pub.dev" + source: hosted + version: "3.4.0+1" term_glyph: dependency: transitive description: @@ -448,6 +821,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" vector_math: dependency: transitive description: @@ -504,6 +885,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" yaml: dependency: transitive description: @@ -514,4 +911,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.11.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.4" diff --git a/recolecta_app/pubspec.yaml b/recolecta_app/pubspec.yaml index 6e88573..aafcb45 100644 --- a/recolecta_app/pubspec.yaml +++ b/recolecta_app/pubspec.yaml @@ -36,6 +36,12 @@ dependencies: cupertino_icons: ^1.0.8 flutter_dotenv: ^6.0.1 flutter_riverpod: ^3.3.1 + dio: ^5.7.0 + go_router: ^14.6.2 + firebase_core: ^3.8.0 + firebase_messaging: ^15.1.5 + flutter_secure_storage: ^9.2.4 + cached_network_image: ^3.4.1 dev_dependencies: flutter_test: diff --git a/recolecta_app/windows/flutter/generated_plugin_registrant.cc b/recolecta_app/windows/flutter/generated_plugin_registrant.cc index 8b6d468..39cedd3 100644 --- a/recolecta_app/windows/flutter/generated_plugin_registrant.cc +++ b/recolecta_app/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,12 @@ #include "generated_plugin_registrant.h" +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/recolecta_app/windows/flutter/generated_plugins.cmake b/recolecta_app/windows/flutter/generated_plugins.cmake index b93c4c3..b1ad9e1 100644 --- a/recolecta_app/windows/flutter/generated_plugins.cmake +++ b/recolecta_app/windows/flutter/generated_plugins.cmake @@ -3,9 +3,12 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core + flutter_secure_storage_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES)