add push notifications for groups and add sender name

This commit is contained in:
otsmr 2025-11-03 00:50:09 +01:00
parent 9356a1fc70
commit c786bb55f9
18 changed files with 514 additions and 282 deletions

View file

@ -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] ?? ""
}

View file

@ -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<D: SwiftProtobuf.Decoder>(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
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -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

View file

@ -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.';
}

View file

@ -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.';
}

View file

@ -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<PushKind>(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 {

View file

@ -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<PushKind> values = <PushKind> [
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);

View file

@ -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 = {

View file

@ -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;
}

View file

@ -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<void> 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<void> showLocalPushNotification(
Future<void> 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<String?> getAvatarIcon(int contactId) async {
return null;
}
String getPushNotificationTextWithoutUserId(PushKind pushKind) {
Map<String, String> 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<String, String> 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] ?? '';
}

View file

@ -201,7 +201,7 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
EncryptedContent content,
) async {
PushKind? kind;
String? reactionContent;
String? additionalContent;
if (content.hasReaction()) {
if (content.reaction.remove) return null;
@ -209,7 +209,9 @@ Future<PushNotification?> 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<PushNotification?> getPushNotificationFromEncryptedContent(
kind = PushKind.reaction;
}
}
reactionContent = content.reaction.emoji;
additionalContent = content.reaction.emoji;
}
if (content.hasTextMessage()) {
@ -270,11 +272,17 @@ Future<PushNotification?> 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;

View file

@ -75,7 +75,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
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!);

View file

@ -77,9 +77,12 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
late StreamSubscription<Group?> userSub;
late StreamSubscription<List<Message>> messageSub;
late StreamSubscription<List<GroupHistory>>? groupActionsSub;
late StreamSubscription<List<Contact>>? contactSub;
late StreamSubscription<Future<List<(Message, Contact)>>>?
lastOpenedMessageByContactSub;
Map<int, Contact> userIdToContact = {};
List<ChatItem> messages = [];
List<Message> allMessages = [];
List<(Message, Contact)> lastOpenedMessageByContact = [];
@ -110,6 +113,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
void dispose() {
userSub.cancel();
messageSub.cancel();
contactSub?.cancel();
groupActionsSub?.cancel();
lastOpenedMessageByContactSub?.cancel();
tutorial?.cancel();
@ -143,6 +147,13 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
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<ChatMessagesView> {
: null,
group: group,
galleryItems: galleryItems,
userIdToContact: userIdToContact,
scrollToMessage: scrollToMessage,
onResponseTriggered: () {
setState(() {

View file

@ -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<int, Contact>? userIdToContact;
final bool hideReactions;
final List<MemoryItem> galleryItems;
final void Function(String)? scrollToMessage;
@ -108,6 +111,8 @@ class _ChatListEntryState extends State<ChatListEntry> {
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<ChatListEntry> {
? ChatTextEntry(
message: widget.message,
nextMessage: widget.nextMessage,
prevMessage: widget.prevMessage,
userIdToContact: widget.userIdToContact,
borderRadius: borderRadius,
minWidth: reactionsForWidth * 43,
)
@ -182,10 +189,21 @@ class _ChatListEntryState extends State<ChatListEntry> {
if (!right && !widget.group.isDirectChat)
hideContactAvatar
? const SizedBox(width: 24)
: AvatarIcon(
: GestureDetector(
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ContactView(widget.message.senderId!),
),
);
},
child: AvatarIcon(
contactId: widget.message.senderId,
fontSize: 12,
),
),
child,
],
),

View file

@ -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<int, Contact>? 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,7 +91,19 @@ class ChatTextEntry extends StatelessWidget {
color: color,
borderRadius: borderRadius,
),
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (displayUserName != '')
Text(
displayUserName,
textAlign: TextAlign.left,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
@ -128,6 +156,8 @@ class ChatTextEntry extends StatelessWidget {
),
],
),
],
),
);
}
}

View file

@ -80,9 +80,9 @@ class _ResponseContainerState extends State<ResponseContainer> {
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),