import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/localization/generated/app_localizations_de.dart'; import 'package:twonly/src/localization/generated/app_localizations_en.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); Future customLocalPushNotification(String title, String msg) async { final androidNotificationDetails = AndroidNotificationDetails( '1', 'System', channelDescription: 'System messages.', importance: Importance.max, priority: Priority.max, styleInformation: BigTextStyleInformation(msg), ); const darwinNotificationDetails = DarwinNotificationDetails(); final notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails, ); await flutterLocalNotificationsPlugin.show( Random.secure().nextInt(9999), title, msg, notificationDetails, ); } Future handlePushData(String pushDataB64) async { try { final pushData = EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64)); PushNotification? pushNotification; PushUser? foundPushUser; if (pushData.keyId == 0) { final key = 'InsecureOnlyUsedForAddingContact'.codeUnits; pushNotification = await tryDecryptMessage(key, pushData); } else { final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys); for (final pushUser in pushUsers) { for (final key in pushUser.pushKeys) { if (key.id == pushData.keyId) { pushNotification = await tryDecryptMessage(key.key, pushData); if (pushNotification != null) { foundPushUser = pushUser; break; } } } // found correct key and user if (foundPushUser != null) break; } } if (pushNotification != null) { if (pushNotification.kind == PushKind.testNotification) { await customLocalPushNotification( 'Test notification', 'This is a test notification.', ); } else if (foundPushUser != null) { if (pushNotification.hasMessageId()) { if (isUUIDNewer( foundPushUser.lastMessageId, pushNotification.messageId, )) { Log.info( 'Got a push notification for a message which was already opened.', ); return; } } await showLocalPushNotification(foundPushUser, pushNotification); } else { await showLocalPushNotificationWithoutUserId(pushNotification); } } } catch (e) { await customLocalPushNotification( 'Du hast eine neue Nachricht.', 'Öffne twonly um mehr zu erfahren.', ); Log.error(e); } } Future tryDecryptMessage( List key, EncryptedPushNotification push, ) async { try { final chacha20 = FlutterChacha20.poly1305Aead(); final secretKeyData = SecretKeyData(key); final secretBox = SecretBox( push.ciphertext, nonce: push.nonce, mac: Mac(push.mac), ); final plaintext = await chacha20.decrypt(secretBox, secretKey: secretKeyData); return PushNotification.fromBuffer(plaintext); } catch (e) { // this error is allowed to happen... return null; } } Future showLocalPushNotification( PushUser pushUser, PushNotification pushNotification, ) async { String? title; String? body; // do not show notification for blocked users... if (pushUser.blocked) { Log.info('Blocked a message from a blocked user!'); return; } title = pushUser.displayName; body = getPushNotificationText(pushNotification); if (body == '') { Log.error('No push notification type defined!'); } FilePathAndroidBitmap? styleInformation; final avatarPath = await getAvatarIcon(pushUser.userId.toInt()); if (avatarPath != null) { styleInformation = FilePathAndroidBitmap(avatarPath); } final lang = getLocalizations(); final androidNotificationDetails = AndroidNotificationDetails( '0', lang.notificationCategoryMessageTitle, channelDescription: lang.notificationCategoryMessageDesc, importance: Importance.max, priority: Priority.max, ticker: 'You got a new message.', largeIcon: styleInformation, ); const darwinNotificationDetails = DarwinNotificationDetails(); final notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails, ); await flutterLocalNotificationsPlugin.show( pushUser.userId.toInt(), title, body, notificationDetails, payload: pushNotification.kind.name, ); } Future showLocalPushNotificationWithoutUserId( PushNotification pushNotification, ) async { String? body; body = getPushNotificationText(pushNotification); final lang = getLocalizations(); final title = lang.notificationTitleUnknownUser; if (body == '') { Log.error('No push notification type defined!'); } final androidNotificationDetails = AndroidNotificationDetails( '0', lang.notificationCategoryMessageTitle, channelDescription: lang.notificationCategoryMessageDesc, importance: Importance.max, priority: Priority.max, ticker: 'You got a new message.', ); const darwinNotificationDetails = DarwinNotificationDetails(); final notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails, ); await flutterLocalNotificationsPlugin.show( 2, title, body, notificationDetails, payload: pushNotification.kind.name, ); } Future getAvatarIcon(int contactId) async { final directory = await getApplicationCacheDirectory(); final avatarsDirectory = Directory('${directory.path}/avatars'); final filePath = '${avatarsDirectory.path}/$contactId.png'; final file = File(filePath); if (file.existsSync()) { return filePath; } return null; } AppLocalizations getLocalizations() { final systemLanguage = Platform.localeName; if (systemLanguage.contains('de')) return AppLocalizationsDe(); return AppLocalizationsEn(); } String getPushNotificationText(PushNotification pushNotification) { final lang = getLocalizations(); var inGroup = ''; if (pushNotification.hasAdditionalContent()) { inGroup = ' ${lang.notificationFillerIn} ${pushNotification.additionalContent}'; } final pushNotificationText = { PushKind.text.name: lang.notificationText(inGroup), PushKind.twonly.name: lang.notificationTwonly(inGroup), PushKind.video.name: lang.notificationVideo(inGroup), PushKind.image.name: lang.notificationImage(inGroup), PushKind.contactRequest.name: lang.notificationContactRequest, PushKind.acceptRequest.name: lang.notificationAcceptRequest, PushKind.storedMediaFile.name: lang.notificationStoredMediaFile, PushKind.reaction.name: lang.notificationReaction, PushKind.reopenedMedia.name: lang.notificationReopenedMedia, PushKind.reactionToVideo.name: lang.notificationReactionToVideo(pushNotification.additionalContent), PushKind.reactionToText.name: lang.notificationReactionToText(pushNotification.additionalContent), PushKind.reactionToImage.name: lang.notificationReactionToImage(pushNotification.additionalContent), PushKind.response.name: lang.notificationResponse(inGroup), PushKind.addedToGroup.name: lang.notificationAddedToGroup(pushNotification.additionalContent), }; return pushNotificationText[pushNotification.kind.name] ?? ''; }