import 'dart:async'; import 'dart:convert'; import 'package:f_r_e_hub/app_state.dart'; import 'package:f_r_e_hub/backend/api_requests/api_calls.dart'; import 'package:flutter/material.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:rxdart/rxdart.dart'; import 'package:f_r_e_hub/components/templates_components/visit_request_template_component/visit_request_template_component_widget.dart'; class PushNotificationService { final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final Subject _onMessage = BehaviorSubject(); final BehaviorSubject _context = BehaviorSubject(); PushNotificationService() { _initializeLocalNotifications(_context); } Future initialize(BuildContext context) async { _context.add(context); await _requestPermissions(); _listenToForegroundMessages(context); _listenToBackgroundMessages(); _listenToNotificationClicks(context); await _updateDeviceToken(); } Future _requestPermissions() async { NotificationSettings settings = await _firebaseMessaging.requestPermission( alert: true, badge: true, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { debugPrint('User granted permission'); } else { debugPrint('User declined or has not accepted permission'); } } Map validJsonFromString(String? string) { // Switch(string != null || string.isNotEmpty) { // case true: // debugPrint() // break; // } String stringValidate = string! .replaceAllMapped(RegExp(r'(\w+):'), (match) => '"${match[1]}":') // Enclose keys in double quotes .replaceAllMapped(RegExp(r':\s*(\w+)'), (match) => ': "${match[1]}"'); Map json = jsonDecode(stringValidate); return json; } void _initializeLocalNotifications( BehaviorSubject context) async { while (context.valueOrNull == null) { await Future.delayed(Duration(milliseconds: 100)); } var initializationSettingsAndroid = AndroidInitializationSettings('mipmap/ic_fre_black'); var initializationSettingsIOS = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); var initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS, ); _flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse response) async { debugPrint('Response payload:${response.payload}'); if (response.payload != null) { // Preprocess the payload to ensure it's in a valid JSON format String validJsonPayload = response.payload! .replaceAllMapped(RegExp(r'(\w+):'), (match) => '"${match[1]}":') // Enclose keys in double quotes .replaceAllMapped( RegExp(r':\s*(\w+)'), (match) => ': "${match[1]}"'); // Enclose string values in double quotes try { Map message = jsonDecode(validJsonPayload); debugPrint('Notification payload: $message'); _handleNotificationClick(message); } catch (e) { debugPrint('Error decoding notification payload: $e'); } } }, ); _createNotificationChannel(); } void _createNotificationChannel() { _flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel( AndroidNotificationChannel( 'channelID', 'channelName', description: 'Channel Description', importance: Importance.max, ), ); } void _listenToForegroundMessages(BuildContext context) { FirebaseMessaging.onMessage.listen((RemoteMessage message) { debugPrint('Got a message whilst in the foreground!'); debugPrint('Message data: ${message.toMap()}'); _onMessage.add(message); _showNotification(message); }); } void _listenToBackgroundMessages() { FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); } void _listenToNotificationClicks(BuildContext context) { FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { debugPrint('Notification clicked!'); _onMessage.add(message); NotificationHandler().handleMessage(message.data, context); }); } Future _updateDeviceToken() async { final NotificationSettings settings = await _requestNotificationPermission(); await _fetchAndLogApnsToken(settings); final String? deviceToken = await FirebaseMessaging.instance.getToken(); if (deviceToken == null) { debugPrint('Failed to get Firebase Messaging token'); return; } debugPrint('Push Messaging token: $deviceToken'); FFAppState().token = deviceToken; final ApiCallResponse? updTokenResponse = await _updateTokenOnServer(deviceToken); if (_isTokenUpdateSuccessful(updTokenResponse)) { debugPrint('Token updated successfully'); } else { debugPrint('Error updating token'); } } Future _requestNotificationPermission() async { final NotificationSettings settings = await _firebaseMessaging.requestPermission(); if (settings.authorizationStatus == AuthorizationStatus.authorized) { debugPrint('User granted permission'); } else { debugPrint('User declined or has not accepted permission'); } return settings; } Future _fetchAndLogApnsToken(NotificationSettings settings) async { if (settings.authorizationStatus == AuthorizationStatus.authorized) { final String? apnsToken = await _firebaseMessaging.getAPNSToken(); if (apnsToken != null) { debugPrint('APNS Token: $apnsToken'); } else { debugPrint('Failed to get APNS token'); } } } Future _updateTokenOnServer(String deviceToken) async { return await PhpGroup.updToken.call( token: deviceToken, devid: FFAppState().devUUID, useruuid: FFAppState().userUUID, ); } bool _isTokenUpdateSuccessful(ApiCallResponse? response) { return PhpGroup.updToken.error((response?.jsonBody ?? '')) == false; } void _showNotification(RemoteMessage message) async { var androidDetails = AndroidNotificationDetails( 'channelID', 'channelName', channelDescription: 'Channel Description', importance: Importance.max, priority: Priority.high, ); var iOSDetails = DarwinNotificationDetails(); var generalNotificationDetails = NotificationDetails(android: androidDetails, iOS: iOSDetails); await _flutterLocalNotificationsPlugin.show( message.hashCode, message.notification?.title, message.notification?.body, generalNotificationDetails, payload: message.data.toString(), ); } _handleNotificationClick(Map payload) { switch (payload.isNotEmpty) { case true: // Print the 'data' property debugPrint('Notification payload: $payload'); // Handle the message data as needed NotificationHandler().handleMessage(payload, _context.value); // Access the 'data' property of 'RemoteMessage' case false: debugPrint('Notification payload is empty'); // Handle the message notification as needed break; } } static Future _firebaseMessagingBackgroundHandler( RemoteMessage message) async { debugPrint('Handling a background message: ${message.messageId}'); } } class NotificationHandler { void handleMessage(Map message, BuildContext context) { debugPrint('Notification Received!'); message.forEach((key, value) { debugPrint('$key: $value'); }); switch (message['type']) { case 'visit_request': _showVisitRequestDialog(message, context); break; case 'visit_response': debugPrint('visit_response'); break; default: debugPrint('Notification type not recognized'); } } void _showVisitRequestDialog( Map message, BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return Dialog( backgroundColor: Colors.transparent, child: VisitRequestTemplateComponentWidget( name: message['nome'] ?? 'Unknown', reason: message['motivo'] ?? 'Unknown', message: message['mensagem'] ?? 'Unknown', document: message['documento'] ?? 'Unknown', ), ); }, ); } }