From c786bb55f9462801b4fe9018808aca8fe2854929 Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 3 Nov 2025 00:50:09 +0100 Subject: [PATCH] add push notifications for groups and add sender name --- .../NotificationService.swift | 84 +++------- .../push_notification.pb.swift | 30 ++-- lib/src/localization/app_de.arb | 21 ++- lib/src/localization/app_en.arb | 19 ++- .../generated/app_localizations.dart | 130 ++++++++++++++-- .../generated/app_localizations_de.dart | 64 +++++++- .../generated/app_localizations_en.dart | 87 +++++++++-- .../generated/push_notification.pb.dart | 16 +- .../generated/push_notification.pbenum.dart | 2 + .../generated/push_notification.pbjson.dart | 12 +- .../protobuf/client/push_notification.proto | 3 +- .../background.notifications.dart | 144 +++++------------- .../notifications/pushkeys.notifications.dart | 18 ++- .../views/camera/share_image_editor_view.dart | 2 +- lib/src/views/chats/chat_messages.view.dart | 12 ++ .../chat_list_entry.dart | 24 ++- .../chat_text_entry.dart | 126 +++++++++------ .../response_container.dart | 2 +- 18 files changed, 514 insertions(+), 282 deletions(-) diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index c2c6048..0091f8f 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -84,7 +84,7 @@ func getPushNotificationData(pushData: String) -> ( pushUser = tryPushUser if isUUIDNewer(pushUser!.lastMessageID, pushNotification!.messageID) { - return ("blocked", "blocked", 0) + //return ("blocked", "blocked", 0) } break } @@ -109,11 +109,12 @@ func getPushNotificationData(pushData: String) -> ( } else if pushUser != nil { return ( pushUser!.displayName, - getPushNotificationText(pushNotification: pushNotification), pushUser!.userID + getPushNotificationText(pushNotification: pushNotification).0, pushUser!.userID ) } else { + let content = getPushNotificationText(pushNotification: pushNotification) return ( - "", getPushNotificationTextWithoutUserId(pushKind: pushNotification.kind), 1 + content.1, content.0, 1 ) } @@ -204,28 +205,31 @@ func readFromKeychain(key: String) -> String? { return nil } -func getPushNotificationText(pushNotification: PushNotification) -> String { +func getPushNotificationText(pushNotification: PushNotification) -> (String, String) { let systemLanguage = Locale.current.language.languageCode?.identifier ?? "en" // Get the current system language var pushNotificationText: [PushKind: String] = [:] + var title = "Someone" // Define the messages based on the system language if systemLanguage.contains("de") { // German + title = "Jemand" pushNotificationText = [ - .text: "hat dir eine Nachricht gesendet.", - .twonly: "hat dir ein twonly gesendet.", - .video: "hat dir ein Video gesendet.", - .image: "hat dir ein Bild gesendet.", + .text: "hat eine Nachricht gesendet.", + .twonly: "hat ein twonly gesendet.", + .video: "hat ein Video gesendet.", + .image: "hat ein Bild gesendet.", .contactRequest: "möchte sich mit dir vernetzen.", .acceptRequest: "ist jetzt mit dir vernetzt.", .storedMediaFile: "hat dein Bild gespeichert.", .reaction: "hat auf dein Bild reagiert.", .testNotification: "Das ist eine Testbenachrichtigung.", .reopenedMedia: "hat dein Bild erneut geöffnet.", - .reactionToVideo: "hat mit {{reaction}} auf dein Video reagiert.", - .reactionToText: "hat mit {{reaction}} auf deinen Text reagiert.", - .reactionToImage: "hat mit {{reaction}} auf dein Bild reagiert.", + .reactionToVideo: "hat mit {{content}} auf dein Video reagiert.", + .reactionToText: "hat mit {{content}} auf deinen Text reagiert.", + .reactionToImage: "hat mit {{content}} auf dein Bild reagiert.", .response: "hat dir geantwortet.", + .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt." ] } else { // Default to English pushNotificationText = [ @@ -239,65 +243,21 @@ func getPushNotificationText(pushNotification: PushNotification) -> String { .reaction: "has reacted to your image.", .testNotification: "This is a test notification.", .reopenedMedia: "has reopened your image.", - .reactionToVideo: "has reacted with {{reaction}} to your video.", - .reactionToText: "has reacted with {{reaction}} to your text.", - .reactionToImage: "has reacted with {{reaction}} to your image.", + .reactionToVideo: "has reacted with {{content}} to your video.", + .reactionToText: "has reacted with {{content}} to your text.", + .reactionToImage: "has reacted with {{content}} to your image.", .response: "has responded.", + .addedToGroup: "has added you to \"{{content}}\"" ] } var content = pushNotificationText[pushNotification.kind] ?? "" - if pushNotification.hasReactionContent { - content.replace("{{reaction}}", with: pushNotification.reactionContent) + if pushNotification.hasAdditionalContent { + content.replace("{{content}}", with: pushNotification.additionalContent) } // Return the corresponding message or an empty string if not found - return content + return (content, title) } -func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String { - let systemLanguage = Locale.current.language.languageCode?.identifier ?? "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.", - .testNotification: "Das ist eine Testbenachrichtigung.", - .reopenedMedia: "hat dein Bild erneut geöffnet.", - .reactionToVideo: "Du hast eine Reaktion auf dein Video erhalten.", - .reactionToText: "Du hast eine Reaktion auf deinen Text erhalten.", - .reactionToImage: "Du hast eine Reaktion auf dein Bild erhalten.", - .response: "Du hast eine Antwort 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.", - .testNotification: "This is a test notification.", - .reopenedMedia: "has reopened your image.", - .reactionToVideo: "You got a reaction to your video.", - .reactionToText: "You got a reaction to your text.", - .reactionToImage: "You got a reaction to your image.", - .response: "You got a response.", - ] - } - - // Return the corresponding message or an empty string if not found - return pushNotificationText[pushKind] ?? "" -} diff --git a/ios/NotificationService/push_notification.pb.swift b/ios/NotificationService/push_notification.pb.swift index 3bddafc..d3dee0c 100644 --- a/ios/NotificationService/push_notification.pb.swift +++ b/ios/NotificationService/push_notification.pb.swift @@ -37,6 +37,7 @@ enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable { case reactionToVideo // = 11 case reactionToText // = 12 case reactionToImage // = 13 + case addedToGroup // = 14 case UNRECOGNIZED(Int) init() { @@ -59,6 +60,7 @@ enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable { case 11: self = .reactionToVideo case 12: self = .reactionToText case 13: self = .reactionToImage + case 14: self = .addedToGroup default: self = .UNRECOGNIZED(rawValue) } } @@ -79,6 +81,7 @@ enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable { case .reactionToVideo: return 11 case .reactionToText: return 12 case .reactionToImage: return 13 + case .addedToGroup: return 14 case .UNRECOGNIZED(let i): return i } } @@ -99,6 +102,7 @@ enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable { .reactionToVideo, .reactionToText, .reactionToImage, + .addedToGroup, ] } @@ -137,21 +141,21 @@ struct PushNotification: Sendable { /// Clears the value of `messageID`. Subsequent reads from it will return its default value. mutating func clearMessageID() {self._messageID = nil} - var reactionContent: String { - get {return _reactionContent ?? String()} - set {_reactionContent = newValue} + var additionalContent: String { + get {return _additionalContent ?? String()} + set {_additionalContent = newValue} } - /// Returns true if `reactionContent` has been explicitly set. - var hasReactionContent: Bool {return self._reactionContent != nil} - /// Clears the value of `reactionContent`. Subsequent reads from it will return its default value. - mutating func clearReactionContent() {self._reactionContent = nil} + /// Returns true if `additionalContent` has been explicitly set. + var hasAdditionalContent: Bool {return self._additionalContent != nil} + /// Clears the value of `additionalContent`. Subsequent reads from it will return its default value. + mutating func clearAdditionalContent() {self._additionalContent = nil} var unknownFields = SwiftProtobuf.UnknownStorage() init() {} fileprivate var _messageID: String? = nil - fileprivate var _reactionContent: String? = nil + fileprivate var _additionalContent: String? = nil } struct PushUsers: Sendable { @@ -214,7 +218,7 @@ struct PushKey: Sendable { // MARK: - Code below here is support for the SwiftProtobuf runtime. extension PushKind: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0reaction\0\u{1}response\0\u{1}text\0\u{1}video\0\u{1}twonly\0\u{1}image\0\u{1}contactRequest\0\u{1}acceptRequest\0\u{1}storedMediaFile\0\u{1}testNotification\0\u{1}reopenedMedia\0\u{1}reactionToVideo\0\u{1}reactionToText\0\u{1}reactionToImage\0") + static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0reaction\0\u{1}response\0\u{1}text\0\u{1}video\0\u{1}twonly\0\u{1}image\0\u{1}contactRequest\0\u{1}acceptRequest\0\u{1}storedMediaFile\0\u{1}testNotification\0\u{1}reopenedMedia\0\u{1}reactionToVideo\0\u{1}reactionToText\0\u{1}reactionToImage\0\u{1}addedToGroup\0") } extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { @@ -264,7 +268,7 @@ extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._Messa extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "PushNotification" - static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{1}messageId\0\u{1}reactionContent\0") + static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{1}messageId\0\u{1}additionalContent\0") mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -274,7 +278,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.kind) }() case 2: try { try decoder.decodeSingularStringField(value: &self._messageID) }() - case 3: try { try decoder.decodeSingularStringField(value: &self._reactionContent) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._additionalContent) }() default: break } } @@ -291,7 +295,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme try { if let v = self._messageID { try visitor.visitSingularStringField(value: v, fieldNumber: 2) } }() - try { if let v = self._reactionContent { + try { if let v = self._additionalContent { try visitor.visitSingularStringField(value: v, fieldNumber: 3) } }() try unknownFields.traverse(visitor: &visitor) @@ -300,7 +304,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme static func ==(lhs: PushNotification, rhs: PushNotification) -> Bool { if lhs.kind != rhs.kind {return false} if lhs._messageID != rhs._messageID {return false} - if lhs._reactionContent != rhs._reactionContent {return false} + if lhs._additionalContent != rhs._additionalContent {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 2e6e6e4..8c167fb 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -773,5 +773,22 @@ "twonlySafeRecoverDesc": "Wenn du ein Backup mit twonly Backup erstellt hast, kannst du es hier wiederherstellen.", "@twonlySafeRecoverDesc": {}, "twonlySafeRecoverBtn": "Backup wiederherstellen", - "@twonlySafeRecoverBtn": {} -} + "@twonlySafeRecoverBtn": {}, + "notificationText": "hat eine Nachricht gesendet.", + "notificationTwonly": "hat ein twonly gesendet.", + "notificationVideo": "hat ein Video gesendet.", + "notificationImage": "hat ein Bild gesendet.", + "notificationAddedToGroup": "hat dich zu \"{groupname}\" hinzugefügt.", + "notificationContactRequest": "möchte sich mit dir vernetzen.", + "notificationAcceptRequest": "ist jetzt mit dir vernetzt.", + "notificationStoredMediaFile": "hat dein Bild gespeichert.", + "notificationReaction": "hat auf dein Bild reagiert.", + "notificationReopenedMedia": "hat dein Bild erneut geöffnet.", + "notificationReactionToVideo": "hat mit {reaction} auf dein Video reagiert.", + "notificationReactionToText": "hat mit {reaction} auf deine Nachricht reagiert.", + "notificationReactionToImage": "hat mit {reaction} auf dein Bild reagiert.", + "notificationResponse": "hat dir geantwortet.", + "notificationTitleUnknownUser": "Jemand", + "notificationCategoryMessageTitle": "Nachrichten", + "notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern." +} \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index a74ea19..f412f7e 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -552,5 +552,22 @@ "youLeftGroup": "You have left the group.", "makerLeftGroup": "{maker} has left the group.", "groupActionYou": "you", - "groupActionYour": "your" + "groupActionYour": "your", + "notificationText": "sent a message.", + "notificationTwonly": "sent a twonly.", + "notificationVideo": "sent a video.", + "notificationImage": "sent a image.", + "notificationAddedToGroup": "has added you to \"{groupname}\"", + "notificationContactRequest": "wants to connect with you.", + "notificationAcceptRequest": "is now connected with you.", + "notificationStoredMediaFile": "has stored your image.", + "notificationReaction": "has reacted to your image.", + "notificationReopenedMedia": "has reopened your image.", + "notificationReactionToVideo": "has reacted with {reaction} to your video.", + "notificationReactionToText": "has reacted with {reaction} to your message.", + "notificationReactionToImage": "has reacted with {reaction} to your image.", + "notificationResponse": "has responded.", + "notificationTitleUnknownUser": "Someone", + "notificationCategoryMessageTitle": "Messages", + "notificationCategoryMessageDesc": "Messages from other users." } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 38d0dbe..1b37278 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2327,85 +2327,85 @@ abstract class AppLocalizations { /// No description provided for @youChangedGroupName. /// /// In en, this message translates to: - /// **'Du hast den Gruppennamen zu „{newGroupName}“ geändert.'** + /// **'You have changed the group name to \"{newGroupName}\".'** String youChangedGroupName(Object newGroupName); /// No description provided for @makerChangedGroupName. /// /// In en, this message translates to: - /// **'{maker} hat den Gruppennamen zu „{newGroupName}“ geändert.'** + /// **'{maker} has changed the group name to \"{newGroupName}\".'** String makerChangedGroupName(Object maker, Object newGroupName); /// No description provided for @youCreatedGroup. /// /// In en, this message translates to: - /// **'Du hast die Gruppe erstellt.'** + /// **'You have created the group.'** String get youCreatedGroup; /// No description provided for @makerCreatedGroup. /// /// In en, this message translates to: - /// **'{maker} hat die Gruppe erstellt.'** + /// **'{maker} has created the group.'** String makerCreatedGroup(Object maker); /// No description provided for @youRemovedMember. /// /// In en, this message translates to: - /// **'Du hast {affected} aus der Gruppe entfernt.'** + /// **'You have removed {affected} from the group.'** String youRemovedMember(Object affected); /// No description provided for @makerRemovedMember. /// /// In en, this message translates to: - /// **'{maker} hat {affected} aus der Gruppe entfernt.'** + /// **'{maker} has removed {affected} from the group.'** String makerRemovedMember(Object affected, Object maker); /// No description provided for @youAddedMember. /// /// In en, this message translates to: - /// **'Du hast {affected} zur Gruppe hinzugefügt.'** + /// **'You have added {affected} to the group.'** String youAddedMember(Object affected); /// No description provided for @makerAddedMember. /// /// In en, this message translates to: - /// **'{maker} hat {affected} zur Gruppe hinzugefügt.'** + /// **'{maker} has added {affected} to the group.'** String makerAddedMember(Object affected, Object maker); /// No description provided for @youMadeAdmin. /// /// In en, this message translates to: - /// **'Du hast {affected} zum Administrator gemacht.'** + /// **'You made {affected} an admin.'** String youMadeAdmin(Object affected); /// No description provided for @makerMadeAdmin. /// /// In en, this message translates to: - /// **'{maker} hat {affected} zum Administrator gemacht.'** + /// **'{maker} made {affected} an admin.'** String makerMadeAdmin(Object affected, Object maker); /// No description provided for @youRevokedAdminRights. /// /// In en, this message translates to: - /// **'Du hast {affectedR} die Administratorrechte entzogen.'** + /// **'You revoked {affectedR} admin rights.'** String youRevokedAdminRights(Object affectedR); /// No description provided for @makerRevokedAdminRights. /// /// In en, this message translates to: - /// **'{maker} hat {affectedR} die Administratorrechte entzogen.'** + /// **'{maker} revoked {affectedR} admin rights.'** String makerRevokedAdminRights(Object affectedR, Object maker); /// No description provided for @youLeftGroup. /// /// In en, this message translates to: - /// **'Du hast die Gruppe verlassen.'** + /// **'You have left the group.'** String get youLeftGroup; /// No description provided for @makerLeftGroup. /// /// In en, this message translates to: - /// **'{maker} hat die Gruppe verlassen.'** + /// **'{maker} has left the group.'** String makerLeftGroup(Object maker); /// No description provided for @groupActionYou. @@ -2419,6 +2419,108 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'your'** String get groupActionYour; + + /// No description provided for @notificationText. + /// + /// In en, this message translates to: + /// **'sent a message.'** + String get notificationText; + + /// No description provided for @notificationTwonly. + /// + /// In en, this message translates to: + /// **'sent a twonly.'** + String get notificationTwonly; + + /// No description provided for @notificationVideo. + /// + /// In en, this message translates to: + /// **'sent a video.'** + String get notificationVideo; + + /// No description provided for @notificationImage. + /// + /// In en, this message translates to: + /// **'sent a image.'** + String get notificationImage; + + /// No description provided for @notificationAddedToGroup. + /// + /// In en, this message translates to: + /// **'has added you to \"{groupname}\"'** + String notificationAddedToGroup(Object groupname); + + /// No description provided for @notificationContactRequest. + /// + /// In en, this message translates to: + /// **'wants to connect with you.'** + String get notificationContactRequest; + + /// No description provided for @notificationAcceptRequest. + /// + /// In en, this message translates to: + /// **'is now connected with you.'** + String get notificationAcceptRequest; + + /// No description provided for @notificationStoredMediaFile. + /// + /// In en, this message translates to: + /// **'has stored your image.'** + String get notificationStoredMediaFile; + + /// No description provided for @notificationReaction. + /// + /// In en, this message translates to: + /// **'has reacted to your image.'** + String get notificationReaction; + + /// No description provided for @notificationReopenedMedia. + /// + /// In en, this message translates to: + /// **'has reopened your image.'** + String get notificationReopenedMedia; + + /// No description provided for @notificationReactionToVideo. + /// + /// In en, this message translates to: + /// **'has reacted with {reaction} to your video.'** + String notificationReactionToVideo(Object reaction); + + /// No description provided for @notificationReactionToText. + /// + /// In en, this message translates to: + /// **'has reacted with {reaction} to your message.'** + String notificationReactionToText(Object reaction); + + /// No description provided for @notificationReactionToImage. + /// + /// In en, this message translates to: + /// **'has reacted with {reaction} to your image.'** + String notificationReactionToImage(Object reaction); + + /// No description provided for @notificationResponse. + /// + /// In en, this message translates to: + /// **'has responded.'** + String get notificationResponse; + + /// No description provided for @notificationTitleUnknownUser. + /// + /// In en, this message translates to: + /// **'Someone'** + String get notificationTitleUnknownUser; + + /// No description provided for @notificationCategoryMessageTitle. + /// + /// In en, this message translates to: + /// **'Messages'** + String get notificationCategoryMessageTitle; + + /// No description provided for @notificationCategoryMessageDesc. + /// + /// In en, this message translates to: + /// **'Messages from other users.'** + String get notificationCategoryMessageDesc; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index ceec42c..0315d9a 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1070,10 +1070,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get twonlySafeRecoverDesc => - 'If you have created a backup with twonly Backup, you can restore it here.'; + 'Wenn du ein Backup mit twonly Backup erstellt hast, kannst du es hier wiederherstellen.'; @override - String get twonlySafeRecoverBtn => 'Restore backup'; + String get twonlySafeRecoverBtn => 'Backup wiederherstellen'; @override String get inviteFriends => 'Freunde einladen'; @@ -1308,4 +1308,64 @@ class AppLocalizationsDe extends AppLocalizations { @override String get groupActionYour => 'deine'; + + @override + String get notificationText => 'hat eine Nachricht gesendet.'; + + @override + String get notificationTwonly => 'hat ein twonly gesendet.'; + + @override + String get notificationVideo => 'hat ein Video gesendet.'; + + @override + String get notificationImage => 'hat ein Bild gesendet.'; + + @override + String notificationAddedToGroup(Object groupname) { + return 'hat dich zu \"$groupname\" hinzugefügt.'; + } + + @override + String get notificationContactRequest => 'möchte sich mit dir vernetzen.'; + + @override + String get notificationAcceptRequest => 'ist jetzt mit dir vernetzt.'; + + @override + String get notificationStoredMediaFile => 'hat dein Bild gespeichert.'; + + @override + String get notificationReaction => 'hat auf dein Bild reagiert.'; + + @override + String get notificationReopenedMedia => 'hat dein Bild erneut geöffnet.'; + + @override + String notificationReactionToVideo(Object reaction) { + return 'hat mit $reaction auf dein Video reagiert.'; + } + + @override + String notificationReactionToText(Object reaction) { + return 'hat mit $reaction auf deine Nachricht reagiert.'; + } + + @override + String notificationReactionToImage(Object reaction) { + return 'hat mit $reaction auf dein Bild reagiert.'; + } + + @override + String get notificationResponse => 'hat dir geantwortet.'; + + @override + String get notificationTitleUnknownUser => 'Jemand'; + + @override + String get notificationCategoryMessageTitle => 'Nachrichten'; + + @override + String get notificationCategoryMessageDesc => + 'Nachrichten von anderen Benutzern.'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 99f7695..032ca02 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1232,68 +1232,68 @@ class AppLocalizationsEn extends AppLocalizations { @override String youChangedGroupName(Object newGroupName) { - return 'Du hast den Gruppennamen zu „$newGroupName“ geändert.'; + return 'You have changed the group name to \"$newGroupName\".'; } @override String makerChangedGroupName(Object maker, Object newGroupName) { - return '$maker hat den Gruppennamen zu „$newGroupName“ geändert.'; + return '$maker has changed the group name to \"$newGroupName\".'; } @override - String get youCreatedGroup => 'Du hast die Gruppe erstellt.'; + String get youCreatedGroup => 'You have created the group.'; @override String makerCreatedGroup(Object maker) { - return '$maker hat die Gruppe erstellt.'; + return '$maker has created the group.'; } @override String youRemovedMember(Object affected) { - return 'Du hast $affected aus der Gruppe entfernt.'; + return 'You have removed $affected from the group.'; } @override String makerRemovedMember(Object affected, Object maker) { - return '$maker hat $affected aus der Gruppe entfernt.'; + return '$maker has removed $affected from the group.'; } @override String youAddedMember(Object affected) { - return 'Du hast $affected zur Gruppe hinzugefügt.'; + return 'You have added $affected to the group.'; } @override String makerAddedMember(Object affected, Object maker) { - return '$maker hat $affected zur Gruppe hinzugefügt.'; + return '$maker has added $affected to the group.'; } @override String youMadeAdmin(Object affected) { - return 'Du hast $affected zum Administrator gemacht.'; + return 'You made $affected an admin.'; } @override String makerMadeAdmin(Object affected, Object maker) { - return '$maker hat $affected zum Administrator gemacht.'; + return '$maker made $affected an admin.'; } @override String youRevokedAdminRights(Object affectedR) { - return 'Du hast $affectedR die Administratorrechte entzogen.'; + return 'You revoked $affectedR admin rights.'; } @override String makerRevokedAdminRights(Object affectedR, Object maker) { - return '$maker hat $affectedR die Administratorrechte entzogen.'; + return '$maker revoked $affectedR admin rights.'; } @override - String get youLeftGroup => 'Du hast die Gruppe verlassen.'; + String get youLeftGroup => 'You have left the group.'; @override String makerLeftGroup(Object maker) { - return '$maker hat die Gruppe verlassen.'; + return '$maker has left the group.'; } @override @@ -1301,4 +1301,63 @@ class AppLocalizationsEn extends AppLocalizations { @override String get groupActionYour => 'your'; + + @override + String get notificationText => 'sent a message.'; + + @override + String get notificationTwonly => 'sent a twonly.'; + + @override + String get notificationVideo => 'sent a video.'; + + @override + String get notificationImage => 'sent a image.'; + + @override + String notificationAddedToGroup(Object groupname) { + return 'has added you to \"$groupname\"'; + } + + @override + String get notificationContactRequest => 'wants to connect with you.'; + + @override + String get notificationAcceptRequest => 'is now connected with you.'; + + @override + String get notificationStoredMediaFile => 'has stored your image.'; + + @override + String get notificationReaction => 'has reacted to your image.'; + + @override + String get notificationReopenedMedia => 'has reopened your image.'; + + @override + String notificationReactionToVideo(Object reaction) { + return 'has reacted with $reaction to your video.'; + } + + @override + String notificationReactionToText(Object reaction) { + return 'has reacted with $reaction to your message.'; + } + + @override + String notificationReactionToImage(Object reaction) { + return 'has reacted with $reaction to your image.'; + } + + @override + String get notificationResponse => 'has responded.'; + + @override + String get notificationTitleUnknownUser => 'Someone'; + + @override + String get notificationCategoryMessageTitle => 'Messages'; + + @override + String get notificationCategoryMessageDesc => 'Messages from other users.'; } diff --git a/lib/src/model/protobuf/client/generated/push_notification.pb.dart b/lib/src/model/protobuf/client/generated/push_notification.pb.dart index eb929fd..c4ad7fe 100644 --- a/lib/src/model/protobuf/client/generated/push_notification.pb.dart +++ b/lib/src/model/protobuf/client/generated/push_notification.pb.dart @@ -114,7 +114,7 @@ class PushNotification extends $pb.GeneratedMessage { factory PushNotification({ PushKind? kind, $core.String? messageId, - $core.String? reactionContent, + $core.String? additionalContent, }) { final $result = create(); if (kind != null) { @@ -123,8 +123,8 @@ class PushNotification extends $pb.GeneratedMessage { if (messageId != null) { $result.messageId = messageId; } - if (reactionContent != null) { - $result.reactionContent = reactionContent; + if (additionalContent != null) { + $result.additionalContent = additionalContent; } return $result; } @@ -135,7 +135,7 @@ class PushNotification extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushNotification', createEmptyInstance: create) ..e(1, _omitFieldNames ? '' : 'kind', $pb.PbFieldType.OE, defaultOrMaker: PushKind.reaction, valueOf: PushKind.valueOf, enumValues: PushKind.values) ..aOS(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId') - ..aOS(3, _omitFieldNames ? '' : 'reactionContent', protoName: 'reactionContent') + ..aOS(3, _omitFieldNames ? '' : 'additionalContent', protoName: 'additionalContent') ..hasRequiredFields = false ; @@ -179,13 +179,13 @@ class PushNotification extends $pb.GeneratedMessage { void clearMessageId() => clearField(2); @$pb.TagNumber(3) - $core.String get reactionContent => $_getSZ(2); + $core.String get additionalContent => $_getSZ(2); @$pb.TagNumber(3) - set reactionContent($core.String v) { $_setString(2, v); } + set additionalContent($core.String v) { $_setString(2, v); } @$pb.TagNumber(3) - $core.bool hasReactionContent() => $_has(2); + $core.bool hasAdditionalContent() => $_has(2); @$pb.TagNumber(3) - void clearReactionContent() => clearField(3); + void clearAdditionalContent() => clearField(3); } class PushUsers extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/client/generated/push_notification.pbenum.dart b/lib/src/model/protobuf/client/generated/push_notification.pbenum.dart index 8bf3ded..e2d05ee 100644 --- a/lib/src/model/protobuf/client/generated/push_notification.pbenum.dart +++ b/lib/src/model/protobuf/client/generated/push_notification.pbenum.dart @@ -28,6 +28,7 @@ class PushKind extends $pb.ProtobufEnum { static const PushKind reactionToVideo = PushKind._(11, _omitEnumNames ? '' : 'reactionToVideo'); static const PushKind reactionToText = PushKind._(12, _omitEnumNames ? '' : 'reactionToText'); static const PushKind reactionToImage = PushKind._(13, _omitEnumNames ? '' : 'reactionToImage'); + static const PushKind addedToGroup = PushKind._(14, _omitEnumNames ? '' : 'addedToGroup'); static const $core.List values = [ reaction, @@ -44,6 +45,7 @@ class PushKind extends $pb.ProtobufEnum { reactionToVideo, reactionToText, reactionToImage, + addedToGroup, ]; static final $core.Map<$core.int, PushKind> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/lib/src/model/protobuf/client/generated/push_notification.pbjson.dart b/lib/src/model/protobuf/client/generated/push_notification.pbjson.dart index 8ff6dc0..9782873 100644 --- a/lib/src/model/protobuf/client/generated/push_notification.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/push_notification.pbjson.dart @@ -31,6 +31,7 @@ const PushKind$json = { {'1': 'reactionToVideo', '2': 11}, {'1': 'reactionToText', '2': 12}, {'1': 'reactionToImage', '2': 13}, + {'1': 'addedToGroup', '2': 14}, ], }; @@ -40,7 +41,7 @@ final $typed_data.Uint8List pushKindDescriptor = $convert.base64Decode( 'VvEAMSCgoGdHdvbmx5EAQSCQoFaW1hZ2UQBRISCg5jb250YWN0UmVxdWVzdBAGEhEKDWFjY2Vw' 'dFJlcXVlc3QQBxITCg9zdG9yZWRNZWRpYUZpbGUQCBIUChB0ZXN0Tm90aWZpY2F0aW9uEAkSEQ' 'oNcmVvcGVuZWRNZWRpYRAKEhMKD3JlYWN0aW9uVG9WaWRlbxALEhIKDnJlYWN0aW9uVG9UZXh0' - 'EAwSEwoPcmVhY3Rpb25Ub0ltYWdlEA0='); + 'EAwSEwoPcmVhY3Rpb25Ub0ltYWdlEA0SEAoMYWRkZWRUb0dyb3VwEA4='); @$core.Deprecated('Use encryptedPushNotificationDescriptor instead') const EncryptedPushNotification$json = { @@ -65,19 +66,20 @@ const PushNotification$json = { '2': [ {'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.PushKind', '10': 'kind'}, {'1': 'messageId', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'messageId', '17': true}, - {'1': 'reactionContent', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'reactionContent', '17': true}, + {'1': 'additionalContent', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'additionalContent', '17': true}, ], '8': [ {'1': '_messageId'}, - {'1': '_reactionContent'}, + {'1': '_additionalContent'}, ], }; /// Descriptor for `PushNotification`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List pushNotificationDescriptor = $convert.base64Decode( 'ChBQdXNoTm90aWZpY2F0aW9uEh0KBGtpbmQYASABKA4yCS5QdXNoS2luZFIEa2luZBIhCgltZX' - 'NzYWdlSWQYAiABKAlIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB' - 'Ug9yZWFjdGlvbkNvbnRlbnSIAQFCDAoKX21lc3NhZ2VJZEISChBfcmVhY3Rpb25Db250ZW50'); + 'NzYWdlSWQYAiABKAlIAFIJbWVzc2FnZUlkiAEBEjEKEWFkZGl0aW9uYWxDb250ZW50GAMgASgJ' + 'SAFSEWFkZGl0aW9uYWxDb250ZW50iAEBQgwKCl9tZXNzYWdlSWRCFAoSX2FkZGl0aW9uYWxDb2' + '50ZW50'); @$core.Deprecated('Use pushUsersDescriptor instead') const PushUsers$json = { diff --git a/lib/src/model/protobuf/client/push_notification.proto b/lib/src/model/protobuf/client/push_notification.proto index 0abeedb..c30d715 100644 --- a/lib/src/model/protobuf/client/push_notification.proto +++ b/lib/src/model/protobuf/client/push_notification.proto @@ -22,12 +22,13 @@ enum PushKind { reactionToVideo = 11; reactionToText = 12; reactionToImage = 13; + addedToGroup = 14; }; message PushNotification { PushKind kind = 1; optional string messageId = 2; - optional string reactionContent = 3; + optional string additionalContent = 3; } diff --git a/lib/src/services/notifications/background.notifications.dart b/lib/src/services/notifications/background.notifications.dart index be510f0..f998723 100644 --- a/lib/src/services/notifications/background.notifications.dart +++ b/lib/src/services/notifications/background.notifications.dart @@ -7,6 +7,9 @@ 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'; @@ -148,10 +151,12 @@ Future showLocalPushNotification( styleInformation = FilePathAndroidBitmap(avatarPath); } + final lang = getLocalizations(); + final androidNotificationDetails = AndroidNotificationDetails( '0', - 'Messages', - channelDescription: 'Messages from other users.', + lang.notificationCategoryMessageTitle, + channelDescription: lang.notificationCategoryMessageDesc, importance: Importance.max, priority: Priority.max, ticker: 'You got a new message.', @@ -176,25 +181,29 @@ Future showLocalPushNotification( Future showLocalPushNotificationWithoutUserId( PushNotification pushNotification, ) async { - String? title; String? body; - body = getPushNotificationTextWithoutUserId(pushNotification.kind); + body = getPushNotificationText(pushNotification); + + final lang = getLocalizations(); + + final title = lang.notificationTitleUnknownUser; + if (body == '') { Log.error('No push notification type defined!'); } - const androidNotificationDetails = AndroidNotificationDetails( + final androidNotificationDetails = AndroidNotificationDetails( '0', - 'Messages', - channelDescription: 'Messages from other users.', + lang.notificationCategoryMessageTitle, + channelDescription: lang.notificationCategoryMessageDesc, importance: Importance.max, priority: Priority.max, ticker: 'You got a new message.', ); const darwinNotificationDetails = DarwinNotificationDetails(); - const notificationDetails = NotificationDetails( + final notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails, ); @@ -219,104 +228,35 @@ Future getAvatarIcon(int contactId) async { return null; } -String getPushNotificationTextWithoutUserId(PushKind pushKind) { - Map pushNotificationText; - +AppLocalizations getLocalizations() { final systemLanguage = Platform.localeName; - - if (systemLanguage.contains('de')) { - pushNotificationText = { - PushKind.text.name: 'Du hast eine neue Nachricht erhalten.', - PushKind.twonly.name: 'Du hast ein neues twonly erhalten.', - PushKind.video.name: 'Du hast ein neues Video erhalten.', - PushKind.image.name: 'Du hast ein neues Bild erhalten.', - PushKind.contactRequest.name: - 'Du hast eine neue 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.reopenedMedia.name: 'Dein Bild wurde erneut geöffnet.', - PushKind.reactionToVideo.name: - 'Du hast eine Reaktion auf dein Video erhalten.', - PushKind.reactionToText.name: - 'Du hast eine Reaktion auf deinen Text erhalten.', - PushKind.reactionToImage.name: - 'Du hast eine Reaktion auf dein Bild erhalten.', - PushKind.response.name: 'Du hast eine Antwort erhalten.', - }; - } else { - pushNotificationText = { - PushKind.text.name: 'You have received a new message.', - PushKind.twonly.name: 'You have received a new twonly.', - PushKind.video.name: 'You have received a new video.', - PushKind.image.name: 'You have received a new image.', - PushKind.contactRequest.name: 'You have received a new contact request.', - PushKind.acceptRequest.name: 'Your contact request has been accepted.', - PushKind.storedMediaFile.name: 'Your image has been saved.', - PushKind.reaction.name: 'You have received a reaction to your image.', - PushKind.reopenedMedia.name: 'Your image has been reopened.', - PushKind.reactionToVideo.name: - 'You have received a reaction to your video.', - PushKind.reactionToText.name: - 'You have received a reaction to your text.', - PushKind.reactionToImage.name: - 'You have received a reaction to your image.', - PushKind.response.name: 'You have received a response.', - }; - } - return pushNotificationText[pushKind.name] ?? ''; + if (systemLanguage.contains('de')) return AppLocalizationsDe(); + return AppLocalizationsEn(); } String getPushNotificationText(PushNotification pushNotification) { - final systemLanguage = Platform.localeName; + final lang = getLocalizations(); - Map pushNotificationText; + final pushNotificationText = { + PushKind.text.name: lang.notificationText, + PushKind.twonly.name: lang.notificationTwonly, + PushKind.video.name: lang.notificationVideo, + PushKind.image.name: lang.notificationImage, + 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, + PushKind.addedToGroup.name: + lang.notificationAddedToGroup(pushNotification.additionalContent), + }; - if (systemLanguage.contains('de')) { - pushNotificationText = { - PushKind.text.name: 'hat dir eine Nachricht gesendet.', - PushKind.twonly.name: 'hat dir ein twonly gesendet.', - PushKind.video.name: 'hat dir ein Video gesendet.', - PushKind.image.name: 'hat dir ein Bild gesendet.', - PushKind.contactRequest.name: 'möchte sich mit 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.reopenedMedia.name: 'hat dein Bild erneut geöffnet.', - PushKind.reactionToVideo.name: - 'hat mit {{reaction}} auf dein Video reagiert.', - PushKind.reactionToText.name: - 'hat mit {{reaction}} auf deine Nachricht reagiert.', - PushKind.reactionToImage.name: - 'hat mit {{reaction}} auf dein Bild reagiert.', - PushKind.response.name: 'hat dir geantwortet.', - }; - } else { - pushNotificationText = { - PushKind.text.name: 'has sent you a message.', - PushKind.twonly.name: 'has sent you a twonly.', - PushKind.video.name: 'has sent you a video.', - PushKind.image.name: 'has sent you an image.', - 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.reopenedMedia.name: 'has reopened your image.', - PushKind.reactionToVideo.name: - 'has reacted with {{reaction}} to your video.', - PushKind.reactionToText.name: - 'has reacted with {{reaction}} to your message.', - PushKind.reactionToImage.name: - 'has reacted with {{reaction}} to your image.', - PushKind.response.name: 'has responded.', - }; - } - var contentText = pushNotificationText[pushNotification.kind.name] ?? ''; - if (pushNotification.hasReactionContent()) { - contentText = contentText.replaceAll( - '{{reaction}}', - pushNotification.reactionContent, - ); - } - return contentText; + return pushNotificationText[pushNotification.kind.name] ?? ''; } diff --git a/lib/src/services/notifications/pushkeys.notifications.dart b/lib/src/services/notifications/pushkeys.notifications.dart index 4a50c61..312c3ad 100644 --- a/lib/src/services/notifications/pushkeys.notifications.dart +++ b/lib/src/services/notifications/pushkeys.notifications.dart @@ -201,7 +201,7 @@ Future getPushNotificationFromEncryptedContent( EncryptedContent content, ) async { PushKind? kind; - String? reactionContent; + String? additionalContent; if (content.hasReaction()) { if (content.reaction.remove) return null; @@ -209,7 +209,9 @@ Future getPushNotificationFromEncryptedContent( final msg = await twonlyDB.messagesDao .getMessageById(content.reaction.targetMessageId) .getSingleOrNull(); - if (msg == null) return null; + if (msg == null || msg.senderId == null || msg.senderId != toUserId) { + return null; + } if (msg.content != null) { kind = PushKind.reactionToText; } else if (msg.mediaId != null) { @@ -224,7 +226,7 @@ Future getPushNotificationFromEncryptedContent( kind = PushKind.reaction; } } - reactionContent = content.reaction.emoji; + additionalContent = content.reaction.emoji; } if (content.hasTextMessage()) { @@ -270,11 +272,17 @@ Future getPushNotificationFromEncryptedContent( } } + if (content.hasGroupCreate()) { + kind = PushKind.addedToGroup; + final group = await twonlyDB.groupsDao.getGroup(content.groupId); + additionalContent = group!.groupName; + } + if (kind == null) return null; final pushNotification = PushNotification()..kind = kind; - if (reactionContent != null) { - pushNotification.reactionContent = reactionContent; + if (additionalContent != null) { + pushNotification.additionalContent = additionalContent; } if (messageId != null) { pushNotification.messageId = messageId; diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 49c2b5f..d1b0112 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -75,7 +75,7 @@ class _ShareImageEditorView extends State { selectedGroupIds.add(widget.sendToGroup!.groupId); } - if (widget.mediaFileService.mediaFile.type == MediaType.video || + if (widget.mediaFileService.mediaFile.type == MediaType.image || widget.mediaFileService.mediaFile.type == MediaType.gif) { if (widget.imageBytesFuture != null) { loadImage(widget.imageBytesFuture!); diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index b6f2d06..ff8857a 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -77,9 +77,12 @@ class _ChatMessagesViewState extends State { late StreamSubscription userSub; late StreamSubscription> messageSub; late StreamSubscription>? groupActionsSub; + late StreamSubscription>? contactSub; late StreamSubscription>>? lastOpenedMessageByContactSub; + Map userIdToContact = {}; + List messages = []; List allMessages = []; List<(Message, Contact)> lastOpenedMessageByContact = []; @@ -110,6 +113,7 @@ class _ChatMessagesViewState extends State { void dispose() { userSub.cancel(); messageSub.cancel(); + contactSub?.cancel(); groupActionsSub?.cancel(); lastOpenedMessageByContactSub?.cancel(); tutorial?.cancel(); @@ -143,6 +147,13 @@ class _ChatMessagesViewState extends State { groupActions = update; await setMessages(allMessages, lastOpenedMessageByContact, update); }); + + final contactsStream = twonlyDB.contactsDao.watchAllContacts(); + contactSub = contactsStream.listen((contacts) { + for (final contact in contacts) { + userIdToContact[contact.userId] = contact; + } + }); } final msgStream = twonlyDB.messagesDao.watchByGroupId(group.groupId); @@ -408,6 +419,7 @@ class _ChatMessagesViewState extends State { : null, group: group, galleryItems: galleryItems, + userIdToContact: userIdToContact, scrollToMessage: scrollToMessage, onResponseTriggered: () { setState(() { diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 646609e..0c48305 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -14,6 +14,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/message_actions. import 'package:twonly/src/views/chats/chat_messages_components/message_context_menu.dart'; import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; +import 'package:twonly/src/views/contact/contact.view.dart'; class ChatListEntry extends StatefulWidget { const ChatListEntry({ @@ -24,6 +25,7 @@ class ChatListEntry extends StatefulWidget { this.onResponseTriggered, this.prevMessage, this.nextMessage, + this.userIdToContact, this.hideReactions = false, super.key, }); @@ -31,6 +33,7 @@ class ChatListEntry extends StatefulWidget { final Message? nextMessage; final Message message; final Group group; + final Map? userIdToContact; final bool hideReactions; final List galleryItems; final void Function(String)? scrollToMessage; @@ -108,6 +111,8 @@ class _ChatListEntryState extends State { ChatTextEntry( message: widget.message, nextMessage: widget.nextMessage, + prevMessage: widget.prevMessage, + userIdToContact: widget.userIdToContact, borderRadius: borderRadius, minWidth: reactionsForWidth * 43, ) @@ -124,6 +129,8 @@ class _ChatListEntryState extends State { ? ChatTextEntry( message: widget.message, nextMessage: widget.nextMessage, + prevMessage: widget.prevMessage, + userIdToContact: widget.userIdToContact, borderRadius: borderRadius, minWidth: reactionsForWidth * 43, ) @@ -182,9 +189,20 @@ class _ChatListEntryState extends State { if (!right && !widget.group.isDirectChat) hideContactAvatar ? const SizedBox(width: 24) - : AvatarIcon( - contactId: widget.message.senderId, - fontSize: 12, + : GestureDetector( + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ContactView(widget.message.senderId!), + ), + ); + }, + child: AvatarIcon( + contactId: widget.message.senderId, + fontSize: 12, + ), ), child, ], diff --git a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart index ee5f4b7..c22df9f 100644 --- a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart' hide TextDirection; +import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -12,13 +13,17 @@ class ChatTextEntry extends StatelessWidget { const ChatTextEntry({ required this.message, required this.nextMessage, + required this.prevMessage, required this.borderRadius, + required this.userIdToContact, required this.minWidth, super.key, }); final Message message; final Message? nextMessage; + final Message? prevMessage; + final Map? userIdToContact; final BorderRadius borderRadius; final double minWidth; @@ -41,6 +46,17 @@ class ChatTextEntry extends StatelessWidget { } var displayTime = !combineTextMessageWithNext(message, nextMessage); + var displayUserName = ''; + if (message.senderId != null && + prevMessage != null && + userIdToContact != null) { + if (!combineTextMessageWithNext(prevMessage!, message)) { + if (userIdToContact![message.senderId] != null) { + displayUserName = + getContactDisplayName(userIdToContact![message.senderId]!); + } + } + } var spacerWidth = minWidth - measureTextWidth(text) - 53; if (spacerWidth < 0) spacerWidth = 0; @@ -75,57 +91,71 @@ class ChatTextEntry extends StatelessWidget { color: color, borderRadius: borderRadius, ), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (expanded) - Expanded( - child: BetterText(text: text, textColor: textColor), - ) - else ...[ - BetterText(text: text, textColor: textColor), - SizedBox( - width: spacerWidth, - ), - ], - if (displayTime || message.modifiedAt != null) - Align( - alignment: AlignmentGeometry.centerRight, - child: Padding( - padding: const EdgeInsets.only(left: 6), - child: Row( - children: [ - if (message.modifiedAt != null) - Padding( - padding: const EdgeInsets.only(right: 5), - child: SizedBox( - height: 10, - child: FaIcon( - FontAwesomeIcons.pencil, - color: Colors.white.withAlpha(150), - size: 10, - ), - ), - ), - Text( - friendlyTime( - context, - (message.modifiedAt != null) - ? message.modifiedAt! - : message.createdAt, - ), - style: TextStyle( - fontSize: 10, - color: Colors.white.withAlpha(150), - decoration: TextDecoration.none, - fontWeight: FontWeight.normal, - ), - ), - ], - ), + if (displayUserName != '') + Text( + displayUserName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, ), ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (expanded) + Expanded( + child: BetterText(text: text, textColor: textColor), + ) + else ...[ + BetterText(text: text, textColor: textColor), + SizedBox( + width: spacerWidth, + ), + ], + if (displayTime || message.modifiedAt != null) + Align( + alignment: AlignmentGeometry.centerRight, + child: Padding( + padding: const EdgeInsets.only(left: 6), + child: Row( + children: [ + if (message.modifiedAt != null) + Padding( + padding: const EdgeInsets.only(right: 5), + child: SizedBox( + height: 10, + child: FaIcon( + FontAwesomeIcons.pencil, + color: Colors.white.withAlpha(150), + size: 10, + ), + ), + ), + Text( + friendlyTime( + context, + (message.modifiedAt != null) + ? message.modifiedAt! + : message.createdAt, + ), + style: TextStyle( + fontSize: 10, + color: Colors.white.withAlpha(150), + decoration: TextDecoration.none, + fontWeight: FontWeight.normal, + ), + ), + ], + ), + ), + ), + ], + ), ], ), ); diff --git a/lib/src/views/chats/chat_messages_components/response_container.dart b/lib/src/views/chats/chat_messages_components/response_container.dart index 404bb40..a0b96da 100644 --- a/lib/src/views/chats/chat_messages_components/response_container.dart +++ b/lib/src/views/chats/chat_messages_components/response_container.dart @@ -80,9 +80,9 @@ class _ResponseContainerState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( + key: _preview, padding: const EdgeInsets.only(top: 4, right: 4, left: 4), child: Container( - key: _preview, width: minWidth, decoration: BoxDecoration( color: context.color.surface.withAlpha(150),