flutter-freaccess-hub/lib/backend/push_notification/pushNotificationService.dart

453 lines
15 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' as math;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hub/actions/actions.dart';
import 'package:hub/app_state.dart';
import 'package:hub/backend/api_requests/api_calls.dart';
import 'package:hub/backend/api_requests/api_manager.dart';
import 'package:hub/components/templates_components/access_notification_modal_template_component/access_notification_modal_template_component_widget.dart';
import 'package:hub/components/templates_components/message_notificaion_modal_template_component/message_notification_widget.dart';
import 'package:hub/components/templates_components/visit_request_template_component/visit_request_template_component_widget.dart';
import 'package:rxdart/rxdart.dart';
//
class PushNotificationService {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final Subject<RemoteMessage> _onMessage = BehaviorSubject<RemoteMessage>();
final BehaviorSubject<BuildContext> _context =
BehaviorSubject<BuildContext>();
final BehaviorSubject<Map<String, dynamic>> _notificationDetails =
BehaviorSubject<Map<String, dynamic>>();
PushNotificationService() {
_initializeLocalNotifications(_context);
_createNotificationChannels();
}
Subject<RemoteMessage> getOnMessage() {
return _onMessage;
}
Future<void> initialize(BuildContext context) async {
_context.add(context);
await _requestPermissions();
_listenToForegroundMessages(context);
_listenToBackgroundMessages();
_listenToNotificationClicks(context);
await updateDeviceToken();
}
Future<void> _requestPermissions() async {
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
log('User granted permission');
} else {
log('User declined or has not accepted permission');
}
}
Map<String, dynamic> validJsonFromString(String? jsonString) {
if (jsonString == null || jsonString.isEmpty) {
return {};
}
// Passo 1 e 2: Adiciona aspas duplas em torno das chaves e valores que não estão corretamente delimitados
String correctedJson = jsonString.replaceAllMapped(
RegExp(r'([a-zA-Z0-9_]+)\s*:\s*([^",\}\]]+)'), (match) {
var key = '"${match[1]!}"'; // Chaves sempre recebem aspas
var value = match[2]!.trim();
// Verifica se o valor é uma string (não numérica, booleana, nula ou objeto JSON)
bool isStringValue = !RegExp(r'^-?\d+(\.\d+)?$').hasMatch(value) &&
value != 'true' &&
value != 'false' &&
value != 'null' &&
!value.startsWith('{') &&
!value.endsWith('}');
// Adiciona aspas duplas em torno do valor se for uma string
String quotedValue = isStringValue ? '"$value"' : value;
return '$key: $quotedValue';
});
// Passo 3: Tratar corretamente strings JSON aninhadas
correctedJson =
correctedJson.replaceAllMapped(RegExp(r'"{([^"]+)}"'), (match) {
// Remove as aspas duplas extras em torno de objetos JSON aninhados
return '{${match[1]!}}';
});
try {
// Passo 4: Decodificar o JSON corrigido
return jsonDecode(correctedJson);
} catch (e) {
log('Error decoding JSON: $e');
return {};
}
}
void _initializeLocalNotifications(
BehaviorSubject<BuildContext> 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 {
log('Response payload:${response.payload}');
if (response.payload != null) {
try {
Map<String, dynamic> message =
validJsonFromString(response.payload!);
log('Notification payload: $message');
var data = _notificationDetails; // Assuming getOnMessage() now returns the latest RemoteMessage
log('Extra: ${data.value}');
_handleNotificationClick(message, extra: data.value);
} catch (e) {
log('Error decoding notification payload: $e');
}
}
},
);
}
void _createNotificationChannels() {
List<String> actions = [
'visit_request',
'visit_response',
'access',
'mensagem',
'enroll_cond',
'miscellaneous'
];
for (String action in actions) {
_createNotificationChannel(action, "Channel for $action");
}
}
void _createNotificationChannel(String channelId, String channelName) {
_flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(
AndroidNotificationChannel(
channelId, // Use o click_action como ID do canal
channelName, // Nome descritivo baseado no click_action
description: 'Channel for $channelName notifications',
importance: Importance.max,
),
);
}
void _listenToForegroundMessages(BuildContext context) {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
log('Got a message whilst in the foreground!');
log('Message data: ${message.toMap()}');
_onMessage.add(message);
_notificationDetails.add(message.toMap()['notification']);
_showNotification(message);
});
}
void _listenToBackgroundMessages() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}
void _listenToNotificationClicks(BuildContext context) {
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
log('Notification clicked!');
_onMessage.add(message);
log('Extra: ${message.notification?.body}');
NotificationHandler().handleMessage(message.data, context); });
}
void configureTokenRefresh() {
_firebaseMessaging.onTokenRefresh.listen(_handleTokenUpdate).onError((err) {
log("Error refreshing token: $err");
});
}
Future<void> _updateToken(String token) async {
FFAppState().token = token;
final ApiCallResponse? response = await _updateTokenOnServer(token);
if (_isTokenUpdateSuccessful(response)) {
log('Token updated successfully on server. Token: $token');
} else {
log('Error updating token on server');
}
}
Future<void> _handleTokenUpdate(String newToken) async {
log('Token refreshed: $newToken');
await _updateToken(newToken);
}
Future<void> updateDeviceToken() async {
configureTokenRefresh();
final NotificationSettings settings =
await _requestNotificationPermission();
if (Platform.isIOS) await _fetchAndLogApnsToken(settings);
final String? deviceToken = await _firebaseMessaging.getToken();
if (deviceToken != null) {
log('Push Messaging token: $deviceToken');
await _updateToken(deviceToken);
} else {
log('Failed to get Firebase Messaging token');
}
}
Future<NotificationSettings> _requestNotificationPermission() async {
final NotificationSettings settings =
await _firebaseMessaging.requestPermission();
log(settings.authorizationStatus == AuthorizationStatus.authorized
? 'User granted permission'
: 'User declined or has not accepted permission');
return settings;
}
Future<void> _fetchAndLogApnsToken(NotificationSettings settings) async {
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
final String? apnsToken = await _firebaseMessaging.getAPNSToken();
log(apnsToken != null
? 'APNS Token: $apnsToken'
: 'Failed to get APNS token');
}
}
Future<ApiCallResponse?> _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;
}
String _getChannelIdBasedOnClickAction(String clickAction) {
final baseId = clickAction.hashCode;
return 'channel_$baseId';
}
void _showNotification(RemoteMessage message) async {
String channelId =
_getChannelIdBasedOnClickAction(message.data['click_action']);
var androidDetails = AndroidNotificationDetails(
channelId,
'Channel Name for $channelId',
channelDescription: 'Channel Description for $channelId',
importance: Importance.max,
priority: Priority.high,
);
var iOSDetails = DarwinNotificationDetails();
var generalNotificationDetails =
NotificationDetails(android: androidDetails, iOS: iOSDetails);
log('Showing notification: ${message.messageId.hashCode}');
await _flutterLocalNotificationsPlugin.show(
// DateTime.now().millisecondsSinceEpoch % (1 << 31),
math.Random().nextInt(1 << 30),
message.notification?.title,
message.notification?.body,
generalNotificationDetails,
payload: message.data.toString(),
);
}
_handleNotificationClick(Map<String, dynamic> payload, {Map<String, dynamic> extra = const {}}) {
switch (payload.isNotEmpty) {
case true:
// Print the 'data' property
log('Notification payload: $payload');
log('Extra: $extra');
// Handle the message data as needed
NotificationHandler().handleMessage(payload, _context.value, extra: extra.isEmpty ? {} : extra);
// Access the 'data' property of 'RemoteMessage'
case false:
log('Notification payload is empty');
// Handle the message notification as needed
break;
}
}
static Future<void> _firebaseMessagingBackgroundHandler(
RemoteMessage message) async {
log('Handling a background message: ${message.messageId}');
}
}
class NotificationHandler {
void handleMessage(Map<String, dynamic> message, BuildContext context, {Map<String, dynamic> extra = const {}}) {
log('Notification Received!');
message.forEach((key, value) {
log('$key: $value');
});
switch (message['click_action']) {
case 'visit_request':
_showVisitRequestDialog(message, context);
break;
case '':
break;
case 'access':
_showAcessNotificationModal(message, context);
break;
case 'mensagem':
_showMessageNotificationDialog(message, context, extra);
break;
case 'enroll_cond':
log('enroll_cond');
break;
default:
log('Notification type not recognized');
}
}
String _getIdBasedOnUserType(Map<String, dynamic> message) {
if (message['USR_TIPO'].toString() == 'O') {
// Retorna USR_ID se não estiver vazio/nulo, caso contrário retorna '0'
return message['USR_ID'].toString().isEmpty
? '0'
: message['USR_ID'].toString();
} else {
// Retorna USR_DOCUMENTO se não estiver vazio/nulo, caso contrário retorna '0'
return message['USR_DOCUMENTO'].toString().isEmpty
? '0'
: message['USR_DOCUMENTO'].toString();
}
}
void _showAcessNotificationModal(
Map<String, dynamic> message, BuildContext context) {
log('Showing access notification dialog');
log('USR_TIPO: ${message['USR_TIPO']}');
log('USR_ID: ${message['USR_ID']}');
log('USR_DOCUMENTO: ${message['USR_DOCUMENTO']}');
showDialog(
context: context,
builder: (BuildContext context) {
_getIdBasedOnUserType(message);
return Dialog(
backgroundColor: Colors.transparent,
child: AccessNotificationModalTemplateComponentWidget(
datetime: message['ACE_DATAHORA'].toString(),
drive: message['ACI_DESCRICAO'].toString(),
id: message['USR_TIPO'].toString() == 'O'
? message['USR_ID'].toString() == ''
? '0'
: message['USR_ID'].toString()
: message['USR_DOCUMENTO'].toString() == ''
? '0'
: message['USR_DOCUMENTO'].toString(),
name: message['PES_NOME'].toString(),
type: message['USR_TIPO'],
));
},
);
}
void _showMessageNotificationDialog(
Map<String, dynamic> message, BuildContext context, Map<String, dynamic> extra) {
log('Showing message notification dialog');
log('Notification "message": $message');
showDialog(
useSafeArea: true,
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
child: MessageNotificationModalTemplateComponentWidget(
id: message['local']['CLI_ID'].toString(),
from: message['remetente'].toString(),
to: message['destinatario'].toString() == 'O' ? 'Morador' : 'Visitante',
message: extra['body'].toString().isEmpty ? 'Unknown' : extra['body'].toString(),
),
);
},
);
}
void _showVisitRequestDialog(
Map<String, dynamic> message, BuildContext context) {
log('Showing visit request notification dialog');
showDialog(
context: context,
builder: (BuildContext context) {
_getIdBasedOnUserType(message);
return Dialog(
backgroundColor: Colors.transparent,
child: VisitRequestTemplateComponentWidget(
vteName: message['mensagem'] ?? 'Unknown',
vteReason: message['motivo'] ?? 'Unknown',
vteMsg: message['mensagem'] ?? 'Unknown',
vteDocument: message['documento'] ?? '',
vteUUID: message['idVisitante'].toString(),
vawRef: message['referencia'].toString(),
vawStatus: 'S',
changeStatusAction: changeStatusAction,
),
);
},
);
}
}
class PushNotificationManager {
final StreamController<RemoteMessage> _onMessageReceivedController =
StreamController<RemoteMessage>.broadcast();
Stream<RemoteMessage> get onMessageReceived =>
_onMessageReceivedController.stream;
PushNotificationManager() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_onMessageReceivedController.add(message);
});
}
void dispose() {
_onMessageReceivedController.close();
}
}