Co-authored-by: Azareth-Tr <Azareth-Tr@users.noreply.github.com> Co-authored-by: eddgranados12 <eddgranados12@users.noreply.github.com> implementacion de login, vistas, correcion de errores en vista registro, domicilios
130 lines
4.4 KiB
Dart
130 lines
4.4 KiB
Dart
// lib/features/notifications/notification_service.dart
|
|
// Gestiona FCM: suscripción a topic, handlers foreground/background.
|
|
//
|
|
// Regla de privacidad: los payloads de push NUNCA contienen lat/lng.
|
|
// El backend solo manda title/body desde notificaciones.json.
|
|
|
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
|
|
// Canal Android de alta prioridad para alertas de proximidad
|
|
const _kChannelId = 'recolecta_alerts';
|
|
const _kChannelName = 'Alertas de recolección';
|
|
const _kChannelDesc = 'Notificaciones de llegada del camión recolector';
|
|
|
|
/// Notifier simple: la EtaScreen lo escucha para refrescar sin polling.
|
|
class _FcmMessageNotifier extends ChangeNotifier {
|
|
RemoteMessage? lastMessage;
|
|
void notify(RemoteMessage msg) {
|
|
lastMessage = msg;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// Handler de background/terminated (top-level, fuera de clase)
|
|
@pragma('vm:entry-point')
|
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
// Solo loguear; la EtaScreen se refrescará cuando la app vuelva a foreground.
|
|
debugPrint('[FCM background] ${message.notification?.title}');
|
|
}
|
|
|
|
class NotificationService {
|
|
NotificationService._();
|
|
|
|
static final _messaging = FirebaseMessaging.instance;
|
|
static final _localNotifications = FlutterLocalNotificationsPlugin();
|
|
static final onFcmMessage = _FcmMessageNotifier();
|
|
|
|
/// Inicializar una sola vez en main.dart
|
|
static Future<void> initialize() async {
|
|
// Registrar handler de background
|
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
|
|
|
// Solicitar permisos (iOS + Android 13+)
|
|
final settings = await _messaging.requestPermission(
|
|
alert: true,
|
|
badge: true,
|
|
sound: true,
|
|
);
|
|
debugPrint('[FCM] Permission: ${settings.authorizationStatus}');
|
|
|
|
// Canal Android
|
|
const androidChannel = AndroidNotificationChannel(
|
|
_kChannelId,
|
|
_kChannelName,
|
|
description: _kChannelDesc,
|
|
importance: Importance.high,
|
|
);
|
|
await _localNotifications
|
|
.resolvePlatformSpecificImplementation<
|
|
AndroidFlutterLocalNotificationsPlugin>()
|
|
?.createNotificationChannel(androidChannel);
|
|
|
|
// Inicializar flutter_local_notifications
|
|
const initSettings = InitializationSettings(
|
|
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
|
iOS: DarwinInitializationSettings(),
|
|
);
|
|
await _localNotifications.initialize(initSettings);
|
|
|
|
// Foreground: mostrar notificación local + notificar EtaScreen
|
|
FirebaseMessaging.onMessage.listen((message) {
|
|
_showLocalNotification(message);
|
|
onFcmMessage.notify(message);
|
|
});
|
|
|
|
// Tap en notificación cuando la app estaba en background
|
|
FirebaseMessaging.onMessageOpenedApp.listen((message) {
|
|
onFcmMessage.notify(message);
|
|
});
|
|
|
|
// Verificar si la app abrió desde una notificación (terminated)
|
|
final initial = await _messaging.getInitialMessage();
|
|
if (initial != null) {
|
|
onFcmMessage.notify(initial);
|
|
}
|
|
}
|
|
|
|
/// Suscribir al topic de la ruta del ciudadano.
|
|
/// Llamar justo después de que verified = true en el domicilio.
|
|
static Future<void> subscribeToRoute(String routeId) async {
|
|
final topic = 'topic_$routeId';
|
|
await _messaging.subscribeToTopic(topic);
|
|
debugPrint('[FCM] Suscrito a $topic');
|
|
}
|
|
|
|
/// Desuscribir (al cambiar de domicilio / colonia)
|
|
static Future<void> unsubscribeFromRoute(String routeId) async {
|
|
final topic = 'topic_$routeId';
|
|
await _messaging.unsubscribeFromTopic(topic);
|
|
debugPrint('[FCM] Desuscrito de $topic');
|
|
}
|
|
|
|
static Future<void> _showLocalNotification(RemoteMessage message) async {
|
|
final notification = message.notification;
|
|
if (notification == null) return;
|
|
|
|
// El payload del backend es solo title+body; NUNCA contiene coordenadas.
|
|
await _localNotifications.show(
|
|
notification.hashCode,
|
|
notification.title,
|
|
notification.body,
|
|
NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
_kChannelId,
|
|
_kChannelName,
|
|
channelDescription: _kChannelDesc,
|
|
importance: Importance.high,
|
|
priority: Priority.high,
|
|
// Sin ningún campo de mapa o ubicación
|
|
),
|
|
iOS: const DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |