simulacion de estados y flujo de notificacion, modificacion de estilos en todas las vistas

This commit is contained in:
shinra32
2026-05-23 07:08:49 -06:00
parent ca076607c7
commit 92f570294a
43 changed files with 4335 additions and 2035 deletions

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@@ -11,6 +12,7 @@ import '../../core/network/api_client.dart';
import '../notifications/notification_service.dart';
import '../../shared/widgets/prevention_banner.dart';
import '../../shared/widgets/progress_steps.dart';
import '../separation_guide/ai_pet_chat_screen.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Modelo de resultado ETA
@@ -75,11 +77,19 @@ class _EtaResult {
// Provider de ETA
// ─────────────────────────────────────────────────────────────────────────────
class _EtaNotifier extends AsyncNotifier<_EtaResult> {
Timer? _timer;
@override
Future<_EtaResult> build() => _fetch();
Future<_EtaResult> build() {
// Consulta silenciosa cada 10 segundos para ver el avance en tiempo real
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 10), (_) => refresh());
ref.onDispose(() => _timer?.cancel());
return _fetch();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(_fetch);
}
@@ -127,6 +137,20 @@ final etaProvider = AsyncNotifierProvider<_EtaNotifier, _EtaResult>(
class ActiveRouteIdNotifier extends Notifier<String?> {
@override
String? build() => null;
void set(String? value) {
debugPrint('📡 [FCM] Evaluando suscripción a la ruta: $value');
if (state != value) {
final oldRoute = state;
state = value;
if (oldRoute != null) NotificationService.unsubscribeFromRoute(oldRoute);
if (value != null) {
NotificationService.subscribeToRoute(
value,
).catchError((e) => debugPrint('❌ Error FCM: $e'));
}
}
}
}
final activeRouteIdProvider = NotifierProvider<ActiveRouteIdNotifier, String?>(
@@ -149,7 +173,7 @@ class _CitizenHomeScreenState extends ConsumerState<CitizenHomeScreen>
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// Refresca al recibir push FCM (RUTA_PROXIMITY, ROUTE_START, etc.)
// Refresca al recibir push FCM (RUTA_PROXIMITY, ROUTE_START, etc.)F
NotificationService.onFcmMessage.addListener(_onPush);
}
@@ -242,11 +266,15 @@ class _EtaContent extends StatelessWidget {
const PreventionBanner(),
const SizedBox(height: 12),
// ── 5. Badge de suscripción FCM ─────────────────────────────────
// ── 5. Banner del Chat IA (Eco) ─────────────────────────────────
const _EcoChatBanner(),
const SizedBox(height: 12),
// ── 6. Badge de suscripción FCM ─────────────────────────────────
const _FcmStatusBadge(),
const SizedBox(height: 16),
// ── 6. Horario semanal ──────────────────────────────────────────
// ── 7. Horario semanal ──────────────────────────────────────────
AppSectionTitle(title: 'Horario del camión'),
_HorarioCard(),
const SizedBox(height: 24),
@@ -256,6 +284,71 @@ class _EtaContent extends StatelessWidget {
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Banner de Eco (Chat IA)
// ─────────────────────────────────────────────────────────────────────────────
class _EcoChatBanner extends StatelessWidget {
const _EcoChatBanner();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const AiPetChatScreen()),
);
},
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.primaryDark,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
boxShadow: AppTheme.softShadow,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Colors.white24,
shape: BoxShape.circle,
),
child: const Icon(
Icons.delete_outline,
color: Colors.white,
size: 28,
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'¿Dudas sobre reciclaje?',
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 4),
Text(
'Pregúntale a Eco, tu asistente inteligente',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
],
),
),
const Icon(Icons.chevron_right, color: Colors.white),
],
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Mapa de ubicación del domicilio (no interactivo)
// ─────────────────────────────────────────────────────────────────────────────
@@ -322,13 +415,13 @@ class _EtaHeroCard extends StatelessWidget {
final cs = Theme.of(context).colorScheme;
if (result.isCompleted) return cs.surfaceContainerHighest;
if (result.isNearby) return const Color(0xFFFFF8E1); // amber-50
return const Color(0xFFE1F5EE); // teal-50
return const Color(0xFFE8D5DB); // rosa claro institucional
}
Color _accentColor(BuildContext context) {
if (result.isCompleted) return Theme.of(context).colorScheme.outline;
if (result.isNearby) return const Color(0xFFBA7517); // amber-400
return const Color(0xFF1D9E75); // teal-400
if (result.isNearby) return const Color(0xFFC8A36A); // beige dorado
return const Color(0xFF9B1B4A); // vino principal
}
@override
@@ -606,7 +699,7 @@ class _FcmStatusBadge extends ConsumerWidget {
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFF1D9E75),
color: Color(0xFF1E7A46),
shape: BoxShape.circle,
),
),