diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 83cfd55..3cfd33a 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -20,7 +20,7 @@ class NotificationService: UNNotificationServiceExtension { if let bestAttemptContent = bestAttemptContent { - guard let _userInfo = bestAttemptContent.userInfo as? [String: Any], + guard let _ = bestAttemptContent.userInfo as? [String: Any], let push_data = bestAttemptContent.userInfo["push_data"] as? String else { return contentHandler(bestAttemptContent); } @@ -109,6 +109,8 @@ func getPushNotificationData(pushDataJson: String) -> (title: String, body: Stri return ("Test Notification", "This is a test notification.") } else if displayName != nil { return (displayName!, getPushNotificationText(pushKind: pushKind)) + } else { + return ("", getPushNotificationTextWithoutUserId(pushKind: pushKind)) } } else { @@ -332,3 +334,37 @@ func getPushNotificationText(pushKind: PushKind) -> String { // Return the corresponding message or an empty string if not found return pushNotificationText[pushKind] ?? "" } + +func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String { + let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language + + var pushNotificationText: [PushKind: String] = [:] + + // Define the messages based on the system language + if systemLanguage.contains("de") { // German + pushNotificationText = [ + .text: "Du hast eine Nachricht erhalten.", + .twonly: "Du hast ein twonly erhalten.", + .video: "Du hast ein Video erhalten.", + .image: "Du hast ein Bild erhalten.", + .contactRequest: "Du hast eine Kontaktanfrage erhalten.", + .acceptRequest: "Deine Kontaktanfrage wurde angenommen.", + .storedMediaFile: "Dein Bild wurde gespeichert.", + .reaction: "Du hast eine Reaktion auf dein Bild erhalten." + ] + } else { // Default to English + pushNotificationText = [ + .text: "You got a message.", + .twonly: "You got a twonly.", + .video: "You got a video.", + .image: "You got an image.", + .contactRequest: "You got a contact request.", + .acceptRequest: "Your contact request has been accepted.", + .storedMediaFile: "Your image has been saved.", + .reaction: "You got a reaction to your image." + ] + } + + // Return the corresponding message or an empty string if not found + return pushNotificationText[pushKind] ?? "" +} \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 933446c..c174ec3 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ diff --git a/lib/src/database/daos/contacts_dao.dart b/lib/src/database/daos/contacts_dao.dart index f43eba5..4e2c376 100644 --- a/lib/src/database/daos/contacts_dao.dart +++ b/lib/src/database/daos/contacts_dao.dart @@ -125,7 +125,7 @@ class ContactsDao extends DatabaseAccessor Future> getAllNotBlockedContacts() { return (select(contacts) - ..where((t) => t.accepted.equals(true) & t.blocked.equals(false)) + ..where((t) => t.blocked.equals(false)) ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) .get(); } diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index bddbdce..517e723 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -70,7 +70,9 @@ class RetransmitMessage { messageId: json['messageId'], userId: json['userId'], bytes: base64Decode(json['bytes']), - pushData: json['pushData'], + pushData: json['pushData'] == null + ? null + : List.from(json['pushData'].map((item) => item as int)), ); } diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index ef38d0a..4f103c6 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -201,6 +201,7 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { final update = ContactsCompanion(accepted: Value(true)); await twonlyDatabase.contactsDao.updateContact(fromUserId, update); notifyContactsAboutProfileChange(); + setupNotificationWithUsers(); break; case MessageKind.profileChange: diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart index 1631961..e2544ed 100644 --- a/lib/src/services/notification_service.dart +++ b/lib/src/services/notification_service.dart @@ -313,6 +313,9 @@ Future handlePushData(String pushDataJson) async { "Test notification", "This is a test notification."); } else if (fromUserId != null) { showLocalPushNotification(fromUserId, pushKind); + } else { + showLocalPushNotificationWithoutUserId(pushKind); + setupNotificationWithUsers(); } } } catch (e) { @@ -336,7 +339,6 @@ Future> getPushKeys(String storageKey) async { pushKeys[int.parse(key)] = PushUser.fromJson(value); }); } - print("read: $storageKey: $pushKeys"); return pushKeys; } @@ -348,7 +350,6 @@ Future setPushKeys(String storageKey, Map pushKeys) async { }); String jsonString = jsonEncode(jsonToSend); - print("write: $storageKey: $pushKeys"); await storage.write( key: storageKey, value: jsonString, @@ -357,45 +358,9 @@ Future setPushKeys(String storageKey, Map pushKeys) async { ); } -/// Streams are created so that app can respond to notification-related events -/// since the plugin is initialized in the `main` function final StreamController selectNotificationStream = StreamController.broadcast(); -const MethodChannel platform = MethodChannel('twonly.eu/notifications'); - -const String portName = 'notification_send_port'; - -class ReceivedNotification { - ReceivedNotification({ - required this.id, - required this.title, - required this.body, - required this.payload, - this.data, - }); - - final int id; - final String? title; - final String? body; - final String? payload; - final Map? data; -} - -String? selectedNotificationPayload; - -/// A notification action which triggers a url launch event -const String urlLaunchActionId = 'id_1'; - -/// A notification action which triggers a App navigation event -const String navigationActionId = 'id_3'; - -/// Defines a iOS/MacOS notification category for text input actions. -const String darwinNotificationCategoryText = 'textCategory'; - -/// Defines a iOS/MacOS notification category for plain actions. -const String darwinNotificationCategoryPlain = 'plainCategory'; - @pragma('vm:entry-point') void notificationTapBackground(NotificationResponse notificationResponse) { // ignore: avoid_print @@ -444,36 +409,6 @@ Future setupPushNotification() async { ); } -Future getAvatarIcon(Contact user) async { - if (user.avatarSvg == null) return null; - - final PictureInfo pictureInfo = - await vg.loadPicture(SvgStringLoader(user.avatarSvg!), null); - - final ui.Image image = await pictureInfo.picture.toImage(300, 300); - - final ByteData? byteData = - await image.toByteData(format: ui.ImageByteFormat.png); - final Uint8List pngBytes = byteData!.buffer.asUint8List(); - - // Get the directory to save the image - final directory = await getApplicationDocumentsDirectory(); - final avatarsDirectory = Directory('${directory.path}/avatars'); - - // Create the avatars directory if it does not exist - if (!await avatarsDirectory.exists()) { - await avatarsDirectory.create(recursive: true); - } - - final filePath = '${avatarsDirectory.path}/${user.userId}.png'; - final file = File(filePath); - await file.writeAsBytes(pngBytes); - - pictureInfo.picture.dispose(); - - return filePath; -} - Future showLocalPushNotification( int fromUserId, PushKind pushKind, @@ -522,6 +457,39 @@ Future showLocalPushNotification( ); } +Future showLocalPushNotificationWithoutUserId( + PushKind pushKind, +) async { + String? title; + String? body; + + body = getPushNotificationTextWithoutUserId(pushKind); + if (body == "") { + Logger("localPushNotificationNewMessage") + .shout("No push notification type defined!"); + } + + AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('0', 'Messages', + channelDescription: 'Messages from other users.', + importance: Importance.max, + priority: Priority.max, + ticker: 'You got a new message.'); + + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails(); + NotificationDetails notificationDetails = NotificationDetails( + android: androidNotificationDetails, iOS: darwinNotificationDetails); + + await flutterLocalNotificationsPlugin.show( + 2, + title, + body, + notificationDetails, + payload: pushKind.name, + ); +} + Future customLocalPushNotification(String title, String msg) async { const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( @@ -545,6 +513,37 @@ Future customLocalPushNotification(String title, String msg) async { ); } +String getPushNotificationTextWithoutUserId(PushKind pushKind) { + Map pushNotificationText; + + String systemLanguage = Platform.localeName; + + if (systemLanguage.contains("de")) { + pushNotificationText = { + PushKind.text.name: "Du hast eine Nachricht erhalten.", + PushKind.twonly.name: "Du hast ein twonly erhalten.", + PushKind.video.name: "Du hast ein Video erhalten.", + PushKind.image.name: "Du hast ein Bild erhalten.", + PushKind.contactRequest.name: "Du hast eine Kontaktanfrage erhalten.", + PushKind.acceptRequest.name: "Deine Kontaktanfrage wurde angenommen.", + PushKind.storedMediaFile.name: "Dein Bild wurde gespeichert.", + PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten." + }; + } else { + pushNotificationText = { + PushKind.text.name: "You got a message.", + PushKind.twonly.name: "You got a twonly.", + PushKind.video.name: "You got a video.", + PushKind.image.name: "You got an image.", + PushKind.contactRequest.name: "You got a contact request.", + PushKind.acceptRequest.name: "Your contact request has been accepted.", + PushKind.storedMediaFile.name: "Your image has been saved.", + PushKind.reaction.name: "You got a reaction to your image." + }; + } + return pushNotificationText[pushKind.name] ?? ""; +} + String getPushNotificationText(PushKind pushKind) { String systemLanguage = Platform.localeName; @@ -575,3 +574,33 @@ String getPushNotificationText(PushKind pushKind) { } return pushNotificationText[pushKind.name] ?? ""; } + +Future getAvatarIcon(Contact user) async { + if (user.avatarSvg == null) return null; + + final PictureInfo pictureInfo = + await vg.loadPicture(SvgStringLoader(user.avatarSvg!), null); + + final ui.Image image = await pictureInfo.picture.toImage(300, 300); + + final ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + final Uint8List pngBytes = byteData!.buffer.asUint8List(); + + // Get the directory to save the image + final directory = await getApplicationDocumentsDirectory(); + final avatarsDirectory = Directory('${directory.path}/avatars'); + + // Create the avatars directory if it does not exist + if (!await avatarsDirectory.exists()) { + await avatarsDirectory.create(recursive: true); + } + + final filePath = '${avatarsDirectory.path}/${user.userId}.png'; + final file = File(filePath); + await file.writeAsBytes(pngBytes); + + pictureInfo.picture.dispose(); + + return filePath; +} diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index bc5cc82..95e8c7f 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -51,15 +51,18 @@ class _SearchUsernameView extends State { return; } - int added = - await twonlyDatabase.contactsDao.insertContact(ContactsCompanion( - username: Value(searchUserName.text), - userId: Value(res.value.userdata.userId.toInt()), - requested: Value(false), - )); + int added = await twonlyDatabase.contactsDao.insertContact( + ContactsCompanion( + username: Value(searchUserName.text), + userId: Value(res.value.userdata.userId.toInt()), + requested: Value(false), + ), + ); if (added > 0) { if (await SignalHelper.addNewContact(res.value.userdata)) { + // before notifying the other party, add + await setupNotificationWithUsers(); encryptAndSendMessage( null, res.value.userdata.userId.toInt(),