From 586c4aa16a34aa4f23628b0a47a4829aad39985c Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 29 Apr 2025 22:26:38 +0200 Subject: [PATCH] fix #65 --- .../NotificationService.swift | 19 ++++--- lib/src/database/tables/messages_table.dart | 1 + lib/src/localization/app_de.arb | 4 +- lib/src/localization/app_en.arb | 2 + .../generated/app_localizations.dart | 6 ++ .../generated/app_localizations_de.dart | 5 +- .../generated/app_localizations_en.dart | 3 + lib/src/model/json/message.dart | 18 +++++- lib/src/providers/api/media_received.dart | 6 ++ lib/src/providers/api/server_messages.dart | 9 ++- lib/src/services/notification_service.dart | 15 +++-- .../views/chats/chat_item_details_view.dart | 56 +++++++++++++++++-- lib/src/views/chats/media_viewer_view.dart | 2 +- .../components/message_send_state_icon.dart | 5 ++ 14 files changed, 129 insertions(+), 22 deletions(-) diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 592bd55..2957a45 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -32,7 +32,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.body = data!.body; bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId) } else { - bestAttemptContent.title = "\(bestAttemptContent.title) [failed to decrypt]" + bestAttemptContent.title = "\(bestAttemptContent.title) [10]" } contentHandler(bestAttemptContent) @@ -60,6 +60,7 @@ enum PushKind: String, Codable { case storedMediaFile case reaction case testNotification + case reopenedMedia } import CryptoKit @@ -106,8 +107,6 @@ func getPushNotificationData(pushDataJson: String) -> (title: String, body: Stri // Handle the push notification based on the pushKind if let pushKind = pushKind { - let bestAttemptContent = UNMutableNotificationContent() - if pushKind == .testNotification { return ("Test Notification", "This is a test notification.", 0) } else if displayName != nil && fromUserId != nil { @@ -180,6 +179,8 @@ func determinePushKind(from message: String) -> PushKind? { return .reaction } else if message.contains("testNotification") { return .testNotification + } else if message.contains("reopenedMedia") { + return .reopenedMedia } else { return nil // Unknown PushKind } @@ -319,7 +320,8 @@ func getPushNotificationText(pushKind: PushKind) -> String { .contactRequest: "möchte sich mit dir vernetzen.", .acceptRequest: "ist jetzt mit dir vernetzt.", .storedMediaFile: "hat dein Bild gespeichert.", - .reaction: "hat auf dein Bild reagiert." + .reaction: "hat auf dein Bild reagiert.", + .reopenedMedia: "Dein Bild wurde erneut geöffnet." ] } else { // Default to English pushNotificationText = [ @@ -330,7 +332,8 @@ func getPushNotificationText(pushKind: PushKind) -> String { .contactRequest: "wants to connect with you.", .acceptRequest: "is now connected with you.", .storedMediaFile: "has stored your image.", - .reaction: "has reacted to your image." + .reaction: "has reacted to your image.", + .reopenedMedia: "Your image was reopened." ] } @@ -353,7 +356,8 @@ func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String { .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." + .reaction: "Du hast eine Reaktion auf dein Bild erhalten.", + .reopenedMedia: "hat dein Bild erneut geöffnet." ] } else { // Default to English pushNotificationText = [ @@ -364,7 +368,8 @@ func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String { .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." + .reaction: "You got a reaction to your image.", + .reopenedMedia: "has reopened your image." ] } diff --git a/lib/src/database/tables/messages_table.dart b/lib/src/database/tables/messages_table.dart index 28279ad..deaa040 100644 --- a/lib/src/database/tables/messages_table.dart +++ b/lib/src/database/tables/messages_table.dart @@ -4,6 +4,7 @@ import 'package:twonly/src/database/tables/contacts_table.dart'; enum MessageKind { textMessage, storedMediaFile, + reopenedMedia, media, contactRequest, profileChange, diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 5e351d5..345b8a4 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -80,7 +80,9 @@ "messageSendState_Sending": "Wird gesendet", "messageSendState_TapToLoad": "Tippe zum Laden", "messageSendState_Loading": "Herunterladen", - "messageStoredInGalery": "In Gallerie gespeichert", + "messageStoredInGalery": "Gespeichert", + "messageReopened": "Erneut geöffnet", + "@messageReopened": {}, "imageEditorDrawOk": "Zeichnung machen", "settingsTitle": "Einstellungen", "settingsChats": "Chats", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 9687915..0ad0aba 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -136,6 +136,8 @@ "@messageSendState_Loading": {}, "messageStoredInGalery": "Stored in gallery", "@messageStoredInGalery": {}, + "messageReopened": "Re-opened", + "@messageReopened": {}, "imageEditorDrawOk": "Take drawing", "@imageEditorDrawOk": {}, "settingsTitle": "Settings", diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index d338657..83786ce 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -491,6 +491,12 @@ abstract class AppLocalizations { /// **'Stored in gallery'** String get messageStoredInGalery; + /// No description provided for @messageReopened. + /// + /// In en, this message translates to: + /// **'Re-opened'** + String get messageReopened; + /// No description provided for @imageEditorDrawOk. /// /// In en, this message translates to: diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index e4afcbb..f4e6266 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -206,7 +206,10 @@ class AppLocalizationsDe extends AppLocalizations { String get messageSendState_Loading => 'Herunterladen'; @override - String get messageStoredInGalery => 'In Gallerie gespeichert'; + String get messageStoredInGalery => 'Gespeichert'; + + @override + String get messageReopened => 'Erneut geöffnet'; @override String get imageEditorDrawOk => 'Zeichnung machen'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 8748f74..a568356 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -208,6 +208,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get messageStoredInGalery => 'Stored in gallery'; + @override + String get messageReopened => 'Re-opened'; + @override String get imageEditorDrawOk => 'Take drawing'; diff --git a/lib/src/model/json/message.dart b/lib/src/model/json/message.dart index ab91e27..b9478c4 100644 --- a/lib/src/model/json/message.dart +++ b/lib/src/model/json/message.dart @@ -19,7 +19,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) { } } } else { - return Colors.black; + return (isDarkMode(context)) ? Colors.white : Colors.black; } } return color; @@ -85,6 +85,8 @@ class MessageContent { return StoredMediaFileContent.fromJson(json); case MessageKind.pushKey: return PushKeyContent.fromJson(json); + case MessageKind.reopenedMedia: + return ReopenedMediaFileContent.fromJson(json); default: return null; } @@ -188,6 +190,20 @@ class StoredMediaFileContent extends MessageContent { } } +class ReopenedMediaFileContent extends MessageContent { + int messageId; + ReopenedMediaFileContent({required this.messageId}); + + static ReopenedMediaFileContent fromJson(Map json) { + return ReopenedMediaFileContent(messageId: json['messageId']); + } + + @override + Map toJson() { + return {'messageId': messageId}; + } +} + class ProfileContent extends MessageContent { String avatarSvg; String displayName; diff --git a/lib/src/providers/api/media_received.dart b/lib/src/providers/api/media_received.dart index 5d9769f..c44d7e9 100644 --- a/lib/src/providers/api/media_received.dart +++ b/lib/src/providers/api/media_received.dart @@ -274,6 +274,12 @@ Future readMediaFile(int mediaId, String type) async { return await file.readAsBytes(); } +Future existsMediaFile(int mediaId, String type) async { + String basePath = await getMediaFilePath(mediaId, "received"); + File file = File("$basePath.$type"); + return await file.exists(); +} + Future writeMediaFile(int mediaId, String type, Uint8List data) async { String basePath = await getMediaFilePath(mediaId, "received"); File file = File("$basePath.$type"); diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index 0622579..762b588 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -130,7 +130,8 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { default: if (message.kind != MessageKind.textMessage && message.kind != MessageKind.media && - message.kind != MessageKind.storedMediaFile) { + message.kind != MessageKind.storedMediaFile && + message.kind != MessageKind.reopenedMedia) { Logger("handleServerMessages") .shout("Got unknown MessageKind $message"); } else if (message.content == null || message.messageId == null) { @@ -147,7 +148,8 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { bool acknowledgeByUser = false; DateTime? openedAt; - if (message.kind == MessageKind.storedMediaFile) { + if (message.kind == MessageKind.storedMediaFile || + message.kind == MessageKind.reopenedMedia) { acknowledgeByUser = true; openedAt = DateTime.now(); } @@ -159,6 +161,9 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { if (content is TextMessageContent) { responseToMessageId = content.responseToMessageId; } + if (content is ReopenedMediaFileContent) { + responseToMessageId = content.messageId; + } if (content is StoredMediaFileContent) { responseToMessageId = content.messageId; await twonlyDatabase.messagesDao.updateMessageByOtherUser( diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart index 2f2df2b..a211ee6 100644 --- a/lib/src/services/notification_service.dart +++ b/lib/src/services/notification_service.dart @@ -163,7 +163,8 @@ enum PushKind { contactRequest, acceptRequest, storedMediaFile, - testNotification + testNotification, + reopenedMedia } extension PushKindExtension on PushKind { @@ -532,7 +533,8 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) { 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." + PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten.", + PushKind.reopenedMedia.name: "Dein Bild wurde erneut geöffnet." }; } else { pushNotificationText = { @@ -543,7 +545,8 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) { 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." + PushKind.reaction.name: "You got a reaction to your image.", + PushKind.reopenedMedia.name: "Your image was reopened." }; } return pushNotificationText[pushKind.name] ?? ""; @@ -563,7 +566,8 @@ String getPushNotificationText(PushKind pushKind) { PushKind.contactRequest.name: "möchte sich mir dir vernetzen.", PushKind.acceptRequest.name: "ist jetzt mit dir vernetzt.", PushKind.storedMediaFile.name: "hat dein Bild gespeichert.", - PushKind.reaction.name: "hat auf dein Bild reagiert." + PushKind.reaction.name: "hat auf dein Bild reagiert.", + PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet." }; } else { pushNotificationText = { @@ -574,7 +578,8 @@ String getPushNotificationText(PushKind pushKind) { PushKind.contactRequest.name: "wants to connect with you.", PushKind.acceptRequest.name: "is now connected with you.", PushKind.storedMediaFile.name: "has stored your image.", - PushKind.reaction.name: "has reacted to your image." + PushKind.reaction.name: "has reacted to your image.", + PushKind.reopenedMedia.name: "has reopened your image." }; } return pushNotificationText[pushKind.name] ?? ""; diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index 3ab1993..1b92aaa 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; +import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; @@ -118,11 +119,35 @@ class ChatListEntry extends StatelessWidget { Widget getReactionRow() { List children = []; bool hasOneTextReaction = false; + bool hasOneStored = false; + bool hasOneReopened = false; for (final reaction in reactions) { MessageContent? content = MessageContent.fromJson( reaction.kind, jsonDecode(reaction.contentJson!)); - if (content is StoredMediaFileContent || message.mediaStored) { + // if (content is StoredMediaFileContent || message.mediaStored) { + // if (hasOneStored) continue; + // hasOneStored = true; + // children.add( + // Expanded( + // child: Align( + // alignment: Alignment.bottomRight, + // child: Padding( + // padding: EdgeInsets.only(right: 3), + // child: FaIcon( + // FontAwesomeIcons.floppyDisk, + // size: 12, + // color: Colors.blue, + // ), + // ), + // ), + // ), + // ); + // } + + if (content is ReopenedMediaFileContent) { + if (hasOneReopened) continue; + hasOneReopened = true; children.add( Expanded( child: Align( @@ -130,16 +155,15 @@ class ChatListEntry extends StatelessWidget { child: Padding( padding: EdgeInsets.only(right: 3), child: FaIcon( - FontAwesomeIcons.floppyDisk, + FontAwesomeIcons.repeat, size: 12, - color: Colors.blue, + color: Colors.yellow, ), ), ), ), ); } - // only show one reaction if (hasOneTextReaction) continue; @@ -282,6 +306,30 @@ class ChatListEntry extends StatelessWidget { ); child = GestureDetector( + onDoubleTap: () async { + if (message.openedAt == null && message.messageOtherId != null) { + return; + } + if (await existsMediaFile(message.messageId, "png")) { + encryptAndSendMessage( + null, + contact.userId, + MessageJson( + kind: MessageKind.reopenedMedia, + messageId: message.messageId, + content: ReopenedMediaFileContent( + messageId: message.messageOtherId!, + ), + timestamp: DateTime.now(), + ), + pushKind: PushKind.reopenedMedia, + ); + await twonlyDatabase.messagesDao.updateMessageByMessageId( + message.messageId, + MessagesCompanion(openedAt: Value(null)), + ); + } + }, onTap: () { if (message.kind == MessageKind.media) { if (message.downloadState == DownloadState.downloaded && diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index 53e656d..e253f35 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -103,7 +103,7 @@ class _MediaViewerViewState extends State { progressTimer?.cancel(); if (allMediaFiles.isNotEmpty) { try { - if (!imageSaved) { + if (!imageSaved && maxShowTime != gMediaShowInfinite) { await deleteMediaFile(allMediaFiles.first.messageId, "mp4"); await deleteMediaFile(allMediaFiles.first.messageId, "png"); } diff --git a/lib/src/views/components/message_send_state_icon.dart b/lib/src/views/components/message_send_state_icon.dart index e442cd0..d78ae97 100644 --- a/lib/src/views/components/message_send_state_icon.dart +++ b/lib/src/views/components/message_send_state_icon.dart @@ -144,6 +144,11 @@ class _MessageSendStateIconState extends State { text = context.lang.messageStoredInGalery; } + if (message.kind == MessageKind.reopenedMedia) { + icon = FaIcon(FontAwesomeIcons.repeat, size: 12, color: color); + text = context.lang.messageReopened; + } + if (message.errorWhileSending) { icon = FaIcon(FontAwesomeIcons.circleExclamation, size: 12, color: color);