diff --git a/NextcloudTalk.xcodeproj/project.pbxproj b/NextcloudTalk.xcodeproj/project.pbxproj index 6f10e1c90..717728fd3 100644 --- a/NextcloudTalk.xcodeproj/project.pbxproj +++ b/NextcloudTalk.xcodeproj/project.pbxproj @@ -204,6 +204,10 @@ 1F7AE07A29142E62009F72AD /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 1F7AE07929142E62009F72AD /* NextcloudKit */; }; 1F7AE07C29142E6A009F72AD /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 1F7AE07B29142E6A009F72AD /* NextcloudKit */; }; 1F7AE07D29158878009F72AD /* IntentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F90EFC225FE489B00F3FA55 /* IntentsUI.framework */; }; + 1F7CCC242D552D2000F3FB77 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7CCC232D552D2000F3FB77 /* Mention.swift */; }; + 1F7CCC252D552D2000F3FB77 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7CCC232D552D2000F3FB77 /* Mention.swift */; }; + 1F7CCC262D552D2000F3FB77 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7CCC232D552D2000F3FB77 /* Mention.swift */; }; + 1F7CCC272D552D2000F3FB77 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7CCC232D552D2000F3FB77 /* Mention.swift */; }; 1F8848122A75B68D00063860 /* IntentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F90EFC225FE489B00F3FA55 /* IntentsUI.framework */; }; 1F8995B32970644C00CABA33 /* ColorGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8995B22970644C00CABA33 /* ColorGenerator.swift */; }; 1F8995B52973547700CABA33 /* WebRTCCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8995B42973547700CABA33 /* WebRTCCommon.swift */; }; @@ -325,6 +329,7 @@ 1FEDE3CE257D43AB00853F79 /* NCMessageFileParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FEDE3CC257D43AB00853F79 /* NCMessageFileParameter.m */; }; 1FEDE3CF257D43AB00853F79 /* NCMessageFileParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FEDE3CC257D43AB00853F79 /* NCMessageFileParameter.m */; }; 1FEDE3D0257D43AB00853F79 /* NCMessageFileParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FEDE3CC257D43AB00853F79 /* NCMessageFileParameter.m */; }; + 1FEF49B62D4D3C37001B400A /* UnitNCMessageParameterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FEF49B52D4D3C37001B400A /* UnitNCMessageParameterTest.swift */; }; 1FF1360F2BFB4F8C006A6101 /* NCRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF1360E2BFB4F8C006A6101 /* NCRoom.swift */; }; 1FF136102BFB4F8C006A6101 /* NCRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF1360E2BFB4F8C006A6101 /* NCRoom.swift */; }; 1FF136112BFB4F8C006A6101 /* NCRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF1360E2BFB4F8C006A6101 /* NCRoom.swift */; }; @@ -787,6 +792,7 @@ 1F785DDA2707865F00AC4B40 /* VoiceMessageTranscribeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VoiceMessageTranscribeViewController.m; sourceTree = ""; }; 1F785DDB2707865F00AC4B40 /* VoiceMessageTranscribeViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VoiceMessageTranscribeViewController.xib; sourceTree = ""; }; 1F785DDC2707865F00AC4B40 /* VoiceMessageTranscribeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VoiceMessageTranscribeViewController.h; sourceTree = ""; }; + 1F7CCC232D552D2000F3FB77 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = ""; }; 1F8995B22970644C00CABA33 /* ColorGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorGenerator.swift; sourceTree = ""; }; 1F8995B42973547700CABA33 /* WebRTCCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCCommon.swift; sourceTree = ""; }; 1F8AAC312C518759004DA20A /* SignalingSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingSettings.swift; sourceTree = ""; }; @@ -860,6 +866,7 @@ 1FEDE3C5257D439500853F79 /* NCChatFileController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCChatFileController.h; sourceTree = ""; }; 1FEDE3CC257D43AB00853F79 /* NCMessageFileParameter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCMessageFileParameter.m; sourceTree = ""; }; 1FEDE3CD257D43AB00853F79 /* NCMessageFileParameter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCMessageFileParameter.h; sourceTree = ""; }; + 1FEF49B52D4D3C37001B400A /* UnitNCMessageParameterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitNCMessageParameterTest.swift; sourceTree = ""; }; 1FF1360E2BFB4F8C006A6101 /* NCRoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCRoom.swift; sourceTree = ""; }; 1FF136142BFB74C3006A6101 /* NCChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCChatMessage.swift; sourceTree = ""; }; 1FF2FD5B2AB99CCB000C9905 /* BroadcastUploadExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BroadcastUploadExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1546,6 +1553,7 @@ 1FBC3BE42B61ACD5003909E0 /* UnitBaseChatViewControllerTest.swift */, 1FB7B9842BE2EE020093CE98 /* UnitChatViewControllerTest.swift */, 1FF4DAA52C08D81D00C1B952 /* UnitNCChatMessageTest.swift */, + 1FEF49B52D4D3C37001B400A /* UnitNCMessageParameterTest.swift */, ); path = Chat; sourceTree = ""; @@ -2185,12 +2193,13 @@ 1FAB2E842ACB482B001214EB /* ChatViewController.swift */, 1F35F8FA2AEEDBC600044BDA /* ChatViewControllerExtension.swift */, 2C4230F62B207AB00013E1FA /* ContextChatViewController.swift */, - 1F0B0A712BA264540073FF8D /* MentionSuggestion.swift */, F644A2DC2CE287FA00E2ED81 /* ChatFileUploader.swift */, 1F2058292CEA404F00AAA673 /* AiSummaryViewController.swift */, 1F20582B2CEA405700AAA673 /* AiSummaryViewController.xib */, 1F205B9F2CEE1B8800AAA673 /* AiSummaryController.swift */, 2C1C68062D51229500A7F98A /* CalendarEvent.swift */, + 1F7CCC232D552D2000F3FB77 /* Mention.swift */, + 1F0B0A712BA264540073FF8D /* MentionSuggestion.swift */, ); name = Chat; sourceTree = ""; @@ -2827,6 +2836,7 @@ 1F6D8C412B2F26D5004376B8 /* TestConstants.swift in Sources */, 1FB7B9872BE441450093CE98 /* UIViewExtensions.swift in Sources */, 1FBC3BE52B61ACD5003909E0 /* UnitBaseChatViewControllerTest.swift in Sources */, + 1FEF49B62D4D3C37001B400A /* UnitNCMessageParameterTest.swift in Sources */, 1F8AAC622C596308004DA20A /* UnitSignalingSettings.swift in Sources */, 1F0B0A772BA26BE10073FF8D /* UnitMentionSuggestionTest.swift in Sources */, 1F6D8C432B2F26EE004376B8 /* Helpers.swift in Sources */, @@ -2858,6 +2868,7 @@ 1F77A5F42AB9A4B2007B6037 /* ABContact.m in Sources */, 1F77A6012AB9A51D007B6037 /* NCNotificationAction.swift in Sources */, 1FB7B9902BF0CDF80093CE98 /* BannedActor.swift in Sources */, + 1F7CCC272D552D2000F3FB77 /* Mention.swift in Sources */, 1F77A5F32AB9A43B007B6037 /* SwiftMarkdownObjCBridge.swift in Sources */, 1FC4B3452CCE671800D28138 /* OcsError.swift in Sources */, 1FF4DA832C025DBF00C1B952 /* NCAPISessionManager.swift in Sources */, @@ -3125,6 +3136,7 @@ 1F35F9042AEEDF0E00044BDA /* AutoCompletionTableViewCell.m in Sources */, 2C42ADB420B58E6300296DEA /* NCChatController.m in Sources */, 1F20582A2CEA404F00AAA673 /* AiSummaryViewController.swift in Sources */, + 1F7CCC242D552D2000F3FB77 /* Mention.swift in Sources */, 1FD9182928C55A73009092AB /* BGTaskHelper.swift in Sources */, 1F66B72929FA936E003FB168 /* SLKDefaultReplyView.m in Sources */, 1F785DDD2707865F00AC4B40 /* VoiceMessageTranscribeViewController.m in Sources */, @@ -3202,6 +3214,7 @@ 1F35F9052AEEDF0E00044BDA /* AutoCompletionTableViewCell.m in Sources */, 1F35F90A2AEEE76A00044BDA /* QuotedMessageView.m in Sources */, 2C62B02E24C1BDD7007E460A /* PlaceholderView.m in Sources */, + 1F7CCC262D552D2000F3FB77 /* Mention.swift in Sources */, 2C62B01024C1BDC5007E460A /* NCRoom.m in Sources */, 1FDCC3ED29EC7E6700DEB39B /* AvatarImageView.swift in Sources */, 1F35F8E32AEEBBE000044BDA /* NCChatTitleView.m in Sources */, @@ -3319,6 +3332,7 @@ 2CC0016924A25C3400A20167 /* NCMessageParameter.m in Sources */, 1FB78E292B6AE8CA00B0D69D /* FederationInvitation.swift in Sources */, 2C444704265D641300DF1DBC /* NCUserDefaults.m in Sources */, + 1F7CCC252D552D2000F3FB77 /* Mention.swift in Sources */, 1F205C512CEF91C500AAA673 /* UserAbsence.swift in Sources */, 2CC001B724A37A9A00A20167 /* NCUser.m in Sources */, 2CC0016124A25B5500A20167 /* NCAPIController.m in Sources */, diff --git a/NextcloudTalk/AvatarManager.swift b/NextcloudTalk/AvatarManager.swift index e2e3c4010..529b511c8 100644 --- a/NextcloudTalk/AvatarManager.swift +++ b/NextcloudTalk/AvatarManager.swift @@ -32,6 +32,11 @@ import SDWebImage return UIImage(named: "group-avatar", in: nil, compatibleWith: traitCollection) } + public func getTeamAvatar(with style: UIUserInterfaceStyle) -> UIImage? { + let traitCollection = UITraitCollection(userInterfaceStyle: style) + return UIImage(named: "team-avatar", in: nil, compatibleWith: traitCollection) + } + public func getMailAvatar(with style: UIUserInterfaceStyle) -> UIImage? { let traitCollection = UITraitCollection(userInterfaceStyle: style) return UIImage(named: "mail-avatar", in: nil, compatibleWith: traitCollection) @@ -86,8 +91,10 @@ import SDWebImage if actorType == NCAttendeeTypeEmail || actorType == NCAttendeeTypeGuest { image = self.getGuestsAvatar(withDisplayName: actorDisplayName ?? "", withStyle: style) - } else if actorType == NCAttendeeTypeGroup || actorType == NCAttendeeTypeCircle { + } else if actorType == NCAttendeeTypeGroup { image = self.getGroupAvatar(with: style) + } else if actorType == NCAttendeeTypeCircle || actorType == NCAttendeeTypeTeams { + image = self.getTeamAvatar(with: style) } else if actorType == "deleted_users" { image = self.getDeletedUserAvatar() } else { diff --git a/NextcloudTalk/BaseChatViewController.swift b/NextcloudTalk/BaseChatViewController.swift index ed4059811..8176d3461 100644 --- a/NextcloudTalk/BaseChatViewController.swift +++ b/NextcloudTalk/BaseChatViewController.swift @@ -563,19 +563,6 @@ import SwiftUI return unmanagedTemporaryMessage } - internal func replaceMessageMentionsKeysWithMentionsDisplayNames(message: String, parameters: String) -> String { - var resultMessage = message.trimmingCharacters(in: .whitespacesAndNewlines) - - guard let messageParametersDict = NCMessageParameter.messageParametersDict(fromJSONString: parameters) else { return resultMessage } - - for (parameterKey, parameter) in messageParametersDict { - let parameterKeyString = "{\(parameterKey)}" - resultMessage = resultMessage.replacingOccurrences(of: parameterKeyString, with: parameter.mentionDisplayName) - } - - return resultMessage - } - internal func appendTemporaryMessage(temporaryMessage: NCChatMessage) { DispatchQueue.main.async { let lastSectionBeforeUpdate = self.dateSections.count - 1 @@ -1017,9 +1004,10 @@ import SwiftUI self.removeUnreadMessagesSeparator() self.removePermanentlyTemporaryMessage(temporaryMessage: message) - guard var originalMessage = message.message else { return } + + guard let originalMessage = message.sendingMessageWithDisplayNames else { return } + if message.messageType != kMessageTypeVoiceMessage { - originalMessage = self.replaceMessageMentionsKeysWithMentionsDisplayNames(message: message.message, parameters: message.messageParametersJSONString ?? "") self.sendChatMessage(message: originalMessage, withParentMessage: message.parent, messageParameters: message.messageParametersJSONString ?? "", silently: message.isSilent) } else { let activeAccount = NCDatabaseManager.sharedInstance().activeAccount() @@ -1109,30 +1097,7 @@ import SwiftUI // Show the message to edit in the reply view self.showReplyView(for: message) self.replyMessageView!.hideCloseButton() - - self.mentionsDict = [:] - - // Try to reconstruct the mentionsDict - for (key, value) in message.messageParameters { - if let key = key as? String, - key.hasPrefix("mention-"), - let value = value as? [String: String] { - - guard let parameter = NCMessageParameter(dictionary: value), - let paramaterDisplayName = parameter.name, - let parameterId = parameter.parameterId - else { continue } - - // For mentions the displayName is in the parameter "name", in our mentionsDict we use - // "mentionsDisplayName" for the displayName with the prefix "@", so we need to construct - // that manually here, so mentions are correctly removed while editing. - // The same needs to happen for "mentionId" -> userId with a prefixed "@" - parameter.mentionDisplayName = "@\(paramaterDisplayName)" - parameter.mentionId = "@\(parameterId)" - self.mentionsDict[key] = parameter - } - } - + self.mentionsDict = message.mentionMessageParameters self.editingMessage = message // For files without a caption we start with an empty text instead of "{file}" diff --git a/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-bright.svg b/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-bright.svg index c675e61dd..78fcc392c 100644 --- a/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-bright.svg +++ b/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-dark.svg b/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-dark.svg index b248f918c..8c4b5cf31 100644 --- a/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-dark.svg +++ b/NextcloudTalk/Images.xcassets/file-avatar.imageset/icon-conversation-text-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-bright.svg b/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-bright.svg index 2ad7cf41a..dc3cf228d 100644 --- a/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-bright.svg +++ b/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-dark.svg b/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-dark.svg index 97529da66..c61b54be0 100644 --- a/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-dark.svg +++ b/NextcloudTalk/Images.xcassets/group-avatar.imageset/icon-conversation-group-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-bright.svg b/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-bright.svg index ac5731914..e6cdf4141 100644 --- a/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-bright.svg +++ b/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-dark.svg b/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-dark.svg index 09a2c8be6..30423fe56 100644 --- a/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-dark.svg +++ b/NextcloudTalk/Images.xcassets/mail-avatar.imageset/icon-conversation-mail-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-bright.svg b/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-bright.svg deleted file mode 100644 index 3d352a53c..000000000 --- a/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-bright.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - diff --git a/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-dark.svg b/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-dark.svg deleted file mode 100644 index db10cab63..000000000 --- a/NextcloudTalk/Images.xcassets/open-avatar.imageset/icon-open-conversation-dark.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - diff --git a/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-bright.svg b/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-bright.svg index de08238bb..d9ae0dd29 100644 --- a/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-bright.svg +++ b/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-dark.svg b/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-dark.svg index ef652ece9..459ca3e7f 100644 --- a/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-dark.svg +++ b/NextcloudTalk/Images.xcassets/password-avatar.imageset/icon-conversation-password-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-bright.svg b/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-bright.svg index 9f4f59e05..f94532b36 100644 --- a/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-bright.svg +++ b/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-dark.svg b/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-dark.svg index 2880e3c76..8ccb683e7 100644 --- a/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-dark.svg +++ b/NextcloudTalk/Images.xcassets/public-avatar.imageset/icon-conversation-public-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/public.imageset/Contents.json b/NextcloudTalk/Images.xcassets/public.imageset/Contents.json deleted file mode 100644 index ac69979b3..000000000 --- a/NextcloudTalk/Images.xcassets/public.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "public.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "public@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "public@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/NextcloudTalk/Images.xcassets/public.imageset/public.png b/NextcloudTalk/Images.xcassets/public.imageset/public.png deleted file mode 100644 index a6cf93e45..000000000 Binary files a/NextcloudTalk/Images.xcassets/public.imageset/public.png and /dev/null differ diff --git a/NextcloudTalk/Images.xcassets/public.imageset/public@2x.png b/NextcloudTalk/Images.xcassets/public.imageset/public@2x.png deleted file mode 100644 index f7d3ec55e..000000000 Binary files a/NextcloudTalk/Images.xcassets/public.imageset/public@2x.png and /dev/null differ diff --git a/NextcloudTalk/Images.xcassets/public.imageset/public@3x.png b/NextcloudTalk/Images.xcassets/public.imageset/public@3x.png deleted file mode 100644 index f4383188a..000000000 Binary files a/NextcloudTalk/Images.xcassets/public.imageset/public@3x.png and /dev/null differ diff --git a/NextcloudTalk/Images.xcassets/open-avatar.imageset/Contents.json b/NextcloudTalk/Images.xcassets/team-avatar.imageset/Contents.json similarity index 76% rename from NextcloudTalk/Images.xcassets/open-avatar.imageset/Contents.json rename to NextcloudTalk/Images.xcassets/team-avatar.imageset/Contents.json index e1ab97cb5..5cfe2fdfc 100644 --- a/NextcloudTalk/Images.xcassets/open-avatar.imageset/Contents.json +++ b/NextcloudTalk/Images.xcassets/team-avatar.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "icon-open-conversation-bright.svg", + "filename" : "icon-team-bright.svg", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "icon-open-conversation-dark.svg", + "filename" : "icon-team-dark.svg", "idiom" : "universal" } ], diff --git a/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-bright.svg b/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-bright.svg new file mode 100644 index 000000000..c3e4faf31 --- /dev/null +++ b/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-bright.svg @@ -0,0 +1,43 @@ + + diff --git a/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-dark.svg b/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-dark.svg new file mode 100644 index 000000000..d17c2226f --- /dev/null +++ b/NextcloudTalk/Images.xcassets/team-avatar.imageset/icon-team-dark.svg @@ -0,0 +1,47 @@ + + + + + + + diff --git a/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-bright.svg b/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-bright.svg index 4e78dd6ed..0fc03f564 100644 --- a/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-bright.svg +++ b/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-bright.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-dark.svg b/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-dark.svg index 55a6dd14c..648339843 100644 --- a/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-dark.svg +++ b/NextcloudTalk/Images.xcassets/user-avatar.imageset/icon-conversation-user-dark.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/NextcloudTalk/InputbarViewController.swift b/NextcloudTalk/InputbarViewController.swift index 38fa6e030..088087d2a 100644 --- a/NextcloudTalk/InputbarViewController.swift +++ b/NextcloudTalk/InputbarViewController.swift @@ -245,8 +245,10 @@ import UIKit guard let messageParametersDict = NCMessageParameter.messageParametersDict(fromJSONString: parameters) else { return resultMessage } for (parameterKey, parameter) in messageParametersDict { + guard let mention = parameter.mention else { continue } + let parameterKeyString = "{\(parameterKey)}" - resultMessage = resultMessage.replacingOccurrences(of: parameter.mentionDisplayName, with: parameterKeyString) + resultMessage = resultMessage.replacingOccurrences(of: mention.labelForChat, with: parameterKeyString) } return resultMessage @@ -295,23 +297,23 @@ import UIKit if let details = suggestion.details { cell.titleLabel.numberOfLines = 2 - let attributedLabel = (suggestion.label + "\n").withFont(.preferredFont(forTextStyle: .body)) + let attributedLabel = (suggestion.mention.label + "\n").withFont(.preferredFont(forTextStyle: .body)) let attributedDetails = details.withFont(.preferredFont(forTextStyle: .callout)).withTextColor(.secondaryLabel) attributedLabel.append(attributedDetails) cell.titleLabel.attributedText = attributedLabel } else { cell.titleLabel.numberOfLines = 1 - cell.titleLabel.text = suggestion.label + cell.titleLabel.text = suggestion.mention.label } if let suggestionUserStatus = suggestion.userStatus { cell.setUserStatus(suggestionUserStatus) } - if suggestion.id == "all" { + if suggestion.mention.id == "all" { cell.avatarButton.setAvatar(for: self.room) } else { - cell.avatarButton.setActorAvatar(forId: suggestion.id, withType: suggestion.source, withDisplayName: suggestion.label, withRoomToken: self.room.token, using: self.account) + cell.avatarButton.setActorAvatar(forId: suggestion.mention.id, withType: suggestion.source, withDisplayName: suggestion.mention.label, withRoomToken: self.room.token, using: self.account) } cell.accessibilityIdentifier = AutoCompletionCellIdentifier @@ -328,7 +330,7 @@ import UIKit let mentionKey = "mention-\(self.mentionsDict.count)" self.mentionsDict[mentionKey] = suggestion.asMessageParameter() - let mentionWithWhitespace = suggestion.label + " " + let mentionWithWhitespace = suggestion.mention.label + " " self.acceptAutoCompletion(with: mentionWithWhitespace, keepPrefix: true) } @@ -356,13 +358,15 @@ import UIKit let substring = (text as NSString).substring(to: cursorOffset) if var lastPossibleMention = substring.components(separatedBy: "@").last { - lastPossibleMention.insert("@", at: lastPossibleMention.startIndex) - for (mentionKey, mentionParameter) in self.mentionsDict { - if lastPossibleMention != mentionParameter.mentionDisplayName { + guard let mention = mentionParameter.mention else { continue } + + if lastPossibleMention != mention.label { continue } + lastPossibleMention.insert("@", at: lastPossibleMention.startIndex) + // Delete mention let range = NSRange(location: cursorOffset - lastPossibleMention.utf16.count, length: lastPossibleMention.utf16.count) textView.text = (text as NSString).replacingCharacters(in: range, with: "") diff --git a/NextcloudTalk/Mention.swift b/NextcloudTalk/Mention.swift new file mode 100644 index 000000000..840c808f4 --- /dev/null +++ b/NextcloudTalk/Mention.swift @@ -0,0 +1,35 @@ +// +// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: GPL-3.0-or-later +// + +import Foundation + +@objcMembers public class Mention: NSObject { + + public var id: String + public var label: String + public var mentionId: String? + + init(id: String, label: String) { + self.id = id + self.label = label + } + + init(id: String, label: String, mentionId: String? = nil) { + self.id = id + self.label = label + self.mentionId = mentionId + } + + public var idForChat: String { + // Prefer mentionId if it's supported by the server + let id = self.mentionId ?? self.id + + return "@\"\(id)\"" + } + + public var labelForChat: String { + return "@\(label)" + } +} diff --git a/NextcloudTalk/MentionSuggestion.swift b/NextcloudTalk/MentionSuggestion.swift index 2488b26ea..3ccf717c3 100644 --- a/NextcloudTalk/MentionSuggestion.swift +++ b/NextcloudTalk/MentionSuggestion.swift @@ -7,48 +7,26 @@ import Foundation @objcMembers public class MentionSuggestion: NSObject { - public var id: String - public var label: String + public var mention: Mention public var source: String - public var mentionId: String? public var userStatus: String? public var details: String? init(dictionary: [String: Any]) { - self.id = dictionary["id"] as? String ?? "" - self.label = dictionary["label"] as? String ?? "" + self.mention = Mention(id: dictionary["id"] as? String ?? "", label: dictionary["label"] as? String ?? "", mentionId: dictionary["mentionId"] as? String) self.source = dictionary["source"] as? String ?? "" - self.mentionId = dictionary["mentionId"] as? String self.userStatus = dictionary["status"] as? String self.details = dictionary["details"] as? String super.init() } - func getIdForChat() -> String { - // When we support a mentionId serverside, we use that - var id = self.mentionId ?? self.id - - if id.contains("/") || id.rangeOfCharacter(from: .whitespaces) != nil { - id = "\"\(id)\"" - } - - return id - } - - func getIdForAvatar() -> String { - // For avatars we always want to use the actorId, so ignore a potential serverside mentionId here - return self.id - } - func asMessageParameter() -> NCMessageParameter { let messageParameter = NCMessageParameter() - messageParameter.parameterId = self.getIdForAvatar() - messageParameter.name = self.label - messageParameter.mentionDisplayName = "@\(self.label)" - // Note: The mentionId on NCMessageParameter is different than the one on MentionSuggestion! - messageParameter.mentionId = "@\(self.getIdForChat())" + messageParameter.parameterId = mention.id + messageParameter.name = mention.label + messageParameter.mention = mention // Set parameter type if self.source == "calls" { @@ -61,6 +39,8 @@ import Foundation messageParameter.type = "user-group" } else if self.source == "emails" { messageParameter.type = "email" + } else if self.source == "teams" { + messageParameter.type = "circle" } return messageParameter diff --git a/NextcloudTalk/NCAPIController.m b/NextcloudTalk/NCAPIController.m index ec8a7dce8..59e41bddc 100644 --- a/NextcloudTalk/NCAPIController.m +++ b/NextcloudTalk/NCAPIController.m @@ -709,7 +709,13 @@ - (NSURLSessionDataTask *)getParticipantsFromRoom:(NSString *)token forAccount:( NSSortDescriptor *customSorting = [NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES comparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { NCRoomParticipant *first = (NCRoomParticipant*)obj1; NCRoomParticipant *second = (NCRoomParticipant*)obj2; - + + BOOL team1 = first.isTeam; + BOOL team2 = second.isTeam; + if (team1 != team2) { + return team1 - team2; + } + BOOL group1 = first.isGroup; BOOL group2 = second.isGroup; if (group1 != group2) { diff --git a/NextcloudTalk/NCChatMessage.m b/NextcloudTalk/NCChatMessage.m index b186860f1..c9738258f 100644 --- a/NextcloudTalk/NCChatMessage.m +++ b/NextcloudTalk/NCChatMessage.m @@ -327,10 +327,7 @@ - (NSMutableAttributedString *)parsedMessage // Default replacement string is the parameter name NSString *replaceString = messageParameter.name; // Format user and call mentions - if ([messageParameter.type isEqualToString:@"user"] || [messageParameter.type isEqualToString:@"guest"] || - [messageParameter.type isEqualToString:@"user-group"] || [messageParameter.type isEqualToString:@"call"] || - [messageParameter.type isEqualToString:@"email"]) { - + if ([messageParameter isMention]) { replaceString = [NSString stringWithFormat:@"@%@", [parameterDict objectForKey:@"name"]]; } parsedMessage = [parsedMessage stringByReplacingOccurrencesOfString:parameter withString:replaceString]; @@ -361,10 +358,7 @@ - (NSMutableAttributedString *)parsedMessage for (NCMessageParameter *param in parameters) { //Set color for mentions - if ([param.type isEqualToString:@"user"] || [param.type isEqualToString:@"guest"] || - [param.type isEqualToString:@"user-group"] || [param.type isEqualToString:@"call"] || - [param.type isEqualToString:@"email"]) { - + if ([param isMention]) { if (param.shouldBeHighlighted) { if (!highlightedColor) { // Only get the elementColor if we really need it to reduce realm queries diff --git a/NextcloudTalk/NCChatMessage.swift b/NextcloudTalk/NCChatMessage.swift index d06a41334..736dc705c 100644 --- a/NextcloudTalk/NCChatMessage.swift +++ b/NextcloudTalk/NCChatMessage.swift @@ -131,6 +131,25 @@ import SwiftyAttributes return dict } + public var mentionMessageParameters: [String: NCMessageParameter] { + var result: [String: NCMessageParameter] = [:] + + for case let (key as String, value as [String: String]) in self.messageParameters { + guard key.hasPrefix("mention-"), let parameter = NCMessageParameter(dictionary: value), parameter.isMention() else { continue } + + if parameter.mention == nil, let parameterId = parameter.parameterId, let paramaterDisplayName = parameter.name { + // Try to reconstruct the mention for unsupported servers + parameter.mention = Mention(id: parameterId, label: paramaterDisplayName) + } + + if parameter.mention != nil { + result[key] = parameter + } + } + + return result + } + // TODO: Should probably be an optional? public var systemMessageFormat: NSMutableAttributedString { guard let message = self.parsedMessage() else { return NSMutableAttributedString(string: "") } @@ -139,14 +158,31 @@ import SwiftyAttributes } // TODO: Should probably be an optional? + /// 'Hello {mention-user1}' -> 'Hello @user1' public var sendingMessage: String { guard var resultMessage = self.message else { return "" } resultMessage = resultMessage.trimmingCharacters(in: .whitespacesAndNewlines) for case let (key as String, value as [AnyHashable: Any]) in self.messageParameters { - if let parameter = NCMessageParameter(dictionary: value) { - resultMessage = resultMessage.replacingOccurrences(of: "{\(key)}", with: parameter.mentionId) + if let parameter = NCMessageParameter(dictionary: value), let mention = parameter.mention { + resultMessage = resultMessage.replacingOccurrences(of: "{\(key)}", with: mention.idForChat) + } + } + + return resultMessage + } + + /// 'Hello {mention-user1}' -> 'Hello @User1 Displayname' + public var sendingMessageWithDisplayNames: String? { + guard var resultMessage = self.message else { return nil } + + resultMessage = message.trimmingCharacters(in: .whitespacesAndNewlines) + + // TODO: Could use mentionMessageParameters directly here? + for case let (key as String, value as [AnyHashable: Any]) in self.messageParameters { + if let parameter = NCMessageParameter(dictionary: value), let mention = parameter.mention { + resultMessage = resultMessage.replacingOccurrences(of: "{\(key)}", with: mention.labelForChat) } } diff --git a/NextcloudTalk/NCMessageParameter.h b/NextcloudTalk/NCMessageParameter.h index 551a3424d..edcb40429 100644 --- a/NextcloudTalk/NCMessageParameter.h +++ b/NextcloudTalk/NCMessageParameter.h @@ -6,6 +6,8 @@ #import #import +@class Mention; + @interface NCMessageParameter : NSObject @property (nonatomic, strong) NSString *parameterId; @@ -15,13 +17,12 @@ @property (nonatomic, assign) NSRange range; @property (nonatomic, strong) NSString *contactName; @property (nonatomic, strong) NSString *contactPhoto; -// Helper property for mentions created using the app -@property (nonatomic, strong) NSString *mentionId; -@property (nonatomic, strong) NSString *mentionDisplayName; +@property (nonatomic, strong) Mention * _Nullable mention; - (instancetype)initWithDictionary:(NSDictionary *)parameterDict; - (BOOL)shouldBeHighlighted; - (UIImage * _Nullable)contactPhotoImage; +- (BOOL)isMention; // parametersDict as [NSString:NCMessageParameter] + (NSString *)messageParametersJSONStringFromDictionary:(NSDictionary *)parametersDict; diff --git a/NextcloudTalk/NCMessageParameter.m b/NextcloudTalk/NCMessageParameter.m index fb658c8ca..753bda6eb 100644 --- a/NextcloudTalk/NCMessageParameter.m +++ b/NextcloudTalk/NCMessageParameter.m @@ -7,6 +7,8 @@ #import "NCDatabaseManager.h" +#import "NextcloudTalk-Swift.h" + @implementation NCMessageParameter - (instancetype)initWithDictionary:(NSDictionary *)parameterDict @@ -31,8 +33,17 @@ - (instancetype)initWithDictionary:(NSDictionary *)parameterDict self.contactName = [parameterDict objectForKey:@"contact-name"]; self.contactPhoto = [parameterDict objectForKey:@"contact-photo"]; - self.mentionId = [parameterDict objectForKey:@"mentionId"]; - self.mentionDisplayName = [parameterDict objectForKey:@"mentionDisplayName"]; + + if ([self isMention]) { + NSString *mentionDisplayName = [parameterDict objectForKey:@"mention-display-name"]; + + if (!mentionDisplayName) { + mentionDisplayName = self.name; + } + + NSString *mentionId = [parameterDict objectForKey:@"mention-id"]; + self.mention = [[Mention alloc] initWithId:self.parameterId label:mentionDisplayName mentionId:mentionId]; + } } return self; @@ -46,6 +57,13 @@ - (BOOL)shouldBeHighlighted return ([_type isEqualToString:@"user"] && [activeAccount.userId isEqualToString:_parameterId]) || [_type isEqualToString:@"call"]; } +- (BOOL)isMention +{ + return [_type isEqualToString:@"user"] || [_type isEqualToString:@"guest"] || + [_type isEqualToString:@"user-group"] || [_type isEqualToString:@"call"] || + [_type isEqualToString:@"email"] || [_type isEqualToString:@"circle"]; +} + - (UIImage *)contactPhotoImage { if (self.contactPhoto) { @@ -83,11 +101,11 @@ + (NSDictionary *)dictionaryFromMessageParameter:(NCMessageParameter *)messagePa if (messageParameter.contactPhoto) { [messageParameterDict setObject:messageParameter.contactPhoto forKey:@"contact-photo"]; } - if (messageParameter.mentionId) { - [messageParameterDict setObject:messageParameter.mentionId forKey:@"mentionId"]; + if (messageParameter.mention.mentionId) { + [messageParameterDict setObject:messageParameter.mention.mentionId forKey:@"mention-id"]; } - if (messageParameter.mentionDisplayName) { - [messageParameterDict setObject:messageParameter.mentionDisplayName forKey:@"mentionDisplayName"]; + if (messageParameter.mention.label) { + [messageParameterDict setObject:messageParameter.mention.label forKey:@"mention-display-name"]; } return [[NSDictionary alloc] initWithDictionary:messageParameterDict]; diff --git a/NextcloudTalk/NCRoomParticipant.h b/NextcloudTalk/NCRoomParticipant.h index 2033c5756..e26945ae8 100644 --- a/NextcloudTalk/NCRoomParticipant.h +++ b/NextcloudTalk/NCRoomParticipant.h @@ -17,6 +17,7 @@ typedef NS_ENUM(NSInteger, NCParticipantType) { extern NSString * const NCAttendeeTypeUser; extern NSString * const NCAttendeeTypeGroup; extern NSString * const NCAttendeeTypeCircle; +extern NSString * const NCAttendeeTypeTeams; extern NSString * const NCAttendeeTypeGuest; extern NSString * const NCAttendeeTypeEmail; extern NSString * const NCAttendeeTypeFederated; @@ -48,7 +49,7 @@ extern NSString * const NCAttendeeBridgeBotId; - (BOOL)isBridgeBotUser; - (BOOL)isGuest; - (BOOL)isGroup; -- (BOOL)isCircle; +- (BOOL)isTeam; - (BOOL)isOffline; - (BOOL)isFederated; - (NSString *)detailedName; diff --git a/NextcloudTalk/NCRoomParticipants.m b/NextcloudTalk/NCRoomParticipants.m index 374e462bf..8b08382ff 100644 --- a/NextcloudTalk/NCRoomParticipants.m +++ b/NextcloudTalk/NCRoomParticipants.m @@ -11,6 +11,7 @@ NSString * const NCAttendeeTypeUser = @"users"; NSString * const NCAttendeeTypeGroup = @"groups"; NSString * const NCAttendeeTypeCircle = @"circles"; +NSString * const NCAttendeeTypeTeams = @"teams"; NSString * const NCAttendeeTypeGuest = @"guests"; NSString * const NCAttendeeTypeEmail = @"emails"; NSString * const NCAttendeeTypeFederated = @"federated_users"; @@ -106,9 +107,9 @@ - (BOOL)isGroup return [_actorType isEqualToString:NCAttendeeTypeGroup]; } -- (BOOL)isCircle +- (BOOL)isTeam { - return [_actorType isEqualToString:NCAttendeeTypeCircle]; + return [_actorType isEqualToString:NCAttendeeTypeCircle] || [_actorType isEqualToString:NCAttendeeTypeTeams]; } - (BOOL)isFederated diff --git a/NextcloudTalk/RoomInfoTableViewController.m b/NextcloudTalk/RoomInfoTableViewController.m index 412484e98..4716e3168 100644 --- a/NextcloudTalk/RoomInfoTableViewController.m +++ b/NextcloudTalk/RoomInfoTableViewController.m @@ -1359,7 +1359,7 @@ - (void)showOptionsForParticipantAtIndexPath:(NSIndexPath *)indexPath } if (canParticipantBeModerated) { - if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityBanV1] && !participant.isGroup && !participant.isCircle && !participant.isFederated) { + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityBanV1] && !participant.isGroup && !participant.isTeam && !participant.isFederated) { NSString *banTitle = NSLocalizedString(@"Ban participant", nil); UIAlertAction *banParticipant = [UIAlertAction actionWithTitle:banTitle @@ -1375,7 +1375,7 @@ - (void)showOptionsForParticipantAtIndexPath:(NSIndexPath *)indexPath NSString *title = NSLocalizedString(@"Remove participant", nil); if (participant.isGroup) { title = NSLocalizedString(@"Remove group and members", nil); - } else if (participant.isCircle) { + } else if (participant.isTeam) { title = NSLocalizedString(@"Remove team and members", nil); } UIAlertAction *removeParticipant = [UIAlertAction actionWithTitle:title diff --git a/NextcloudTalkTests/UI/UIRoomTest.swift b/NextcloudTalkTests/UI/UIRoomTest.swift index 6e0a808b8..bb5033f4b 100644 --- a/NextcloudTalkTests/UI/UIRoomTest.swift +++ b/NextcloudTalkTests/UI/UIRoomTest.swift @@ -166,7 +166,41 @@ final class UIRoomTest: XCTestCase { XCTAssert(app.images["MessageSent"].waitForExistence(timeout: TestConstants.timeoutShort)) let tables = app.tables - XCTAssert(tables.textViews["@" + newConversationName].waitForExistence(timeout: TestConstants.timeoutShort)) + var messageTextView = tables.textViews["@\(newConversationName)"] + XCTAssert(messageTextView.waitForExistence(timeout: TestConstants.timeoutShort)) + + // Open context menu + messageTextView.descendants(matching: .any)["@\(newConversationName)"].press(forDuration: 2.0) + + // Check if 'Edit' exists + let editButton = app.buttons["Edit"] + let editExists = editButton.waitForExistence(timeout: TestConstants.timeoutShort) + + // Edit might not be supported by the server. Check for reply button to ensure context menu was correctly displayed + if !editExists { + if !app.buttons["Reply"].exists { + XCTFail("Neither edit, nor reply button exist") + } + + return + } + + editButton.tap() + + // Wait for the original text to be shown in the textView + var predicate = NSPredicate(format: "value == '@\(newConversationName)'") + var textViewValue = toolbar.descendants(matching: .any).containing(predicate).firstMatch + XCTAssert(textViewValue.waitForExistence(timeout: TestConstants.timeoutShort)) + + textView.typeText(" Edited") + + // TODO: Should change the lib to have a proper identifier here + // Save the edit + toolbar.buttons["selected"].tap() + + // Check if the edit is correct + messageTextView = tables.textViews["@\(newConversationName) Edited"] + XCTAssert(messageTextView.waitForExistence(timeout: TestConstants.timeoutShort)) } func testLobbyView() { diff --git a/NextcloudTalkTests/Unit/Chat/UnitNCChatMessageTest.swift b/NextcloudTalkTests/Unit/Chat/UnitNCChatMessageTest.swift index b456ed7b4..128c754bc 100644 --- a/NextcloudTalkTests/Unit/Chat/UnitNCChatMessageTest.swift +++ b/NextcloudTalkTests/Unit/Chat/UnitNCChatMessageTest.swift @@ -50,4 +50,58 @@ final class UnitNCChatMessageTest: TestBaseRealm { XCTAssertEqual(mentionMessage.parsedMarkdownForChat().string, "@Username with space{mention-user2}") } + func testMentionParameters() throws { + let messageParameters = """ + { + "actor": { + "type": "user", + "id": "admin", + "name": "admin ABC", + "mention-id": "admin" + }, + "mention-federated-user1": { + "type": "user", + "id": "user1", + "name": "User1 Displayname", + "server": "https://nextcloud.local", + "mention-id": "federated_user/user1@nextcloud.local" + }, + "mention-user1": { + "type": "user", + "id": "alice", + "name": "alice", + "mention-id": "alice" + }, + "mention-call1": { + "type": "call", + "id": "12345", + "name": "Group Conversation", + "call-type": "public", + "icon-url": "https://nextcloud.local/ocs/v2.php/apps/spreed/api/v1/room/12345/avatar?v=1b893bde", + "mention-id": "all" + } + } + """ + + let message = NCChatMessage() + message.messageParametersJSONString = messageParameters + + message.message = "Hello {mention-user1} --- hello {mention-federated-user1} --- hello {mention-call1} 123" + XCTAssertEqual(message.parsedMarkdownForChat().string, "Hello @alice --- hello @User1 Displayname --- hello @Group Conversation 123") + + let mentionsDict = message.mentionMessageParameters + XCTAssertEqual(mentionsDict.count, 3) + + let userMention = mentionsDict.first(where: { $0.value.type == "user" && !$0.key.contains("federated") })!.value + XCTAssertEqual(userMention.mention?.mentionId, "alice") + + let federatedMention = mentionsDict.first(where: { $0.value.type == "user" && $0.key.contains("federated") })!.value + XCTAssertEqual(federatedMention.mention?.mentionId, "federated_user/user1@nextcloud.local") + + let callMention = mentionsDict.first(where: { $0.value.type == "call" })!.value + XCTAssertEqual(callMention.mention?.mentionId, "all") + + XCTAssertEqual(message.sendingMessage, "Hello @\"alice\" --- hello @\"federated_user/user1@nextcloud.local\" --- hello @\"all\" 123") + XCTAssertEqual(message.sendingMessageWithDisplayNames, "Hello @alice --- hello @User1 Displayname --- hello @Group Conversation 123") + } } diff --git a/NextcloudTalkTests/Unit/Chat/UnitNCMessageParameterTest.swift b/NextcloudTalkTests/Unit/Chat/UnitNCMessageParameterTest.swift new file mode 100644 index 000000000..03a859c27 --- /dev/null +++ b/NextcloudTalkTests/Unit/Chat/UnitNCMessageParameterTest.swift @@ -0,0 +1,55 @@ +// +// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: GPL-3.0-or-later +// + +import XCTest +@testable import NextcloudTalk + +final class UnitNCMessageParameterTest: XCTestCase { + + func testMentionIdFromServerLocal() throws { + let data = [ + "id": "admin", + "mention-id": "admin", + "name": "admin displayname", + "type": "user" + ] + + let parameters = NCMessageParameter(dictionary: data) + + guard let parameters else { + XCTFail("Failed to create message parameters with dictionary") + return + } + + XCTAssertEqual(parameters.parameterId, "admin") + XCTAssertEqual(parameters.name, "admin displayname") + XCTAssertEqual(parameters.mention?.id, "admin") + XCTAssertEqual(parameters.mention?.idForChat, "@\"admin\"") + XCTAssertEqual(parameters.mention?.label, "admin displayname") + XCTAssertEqual(parameters.mention?.labelForChat, "@admin displayname") + } + + func testMentionIdFromServerFederated() throws { + let data = [ + "id": "admin", + "mention-id": "federated_user/admin@nextcloud.local", + "name": "admin displayname", + "server": "https://nextcloud.local", + "type": "user" + ] + + let parameters = NCMessageParameter(dictionary: data) + + guard let parameters else { + XCTFail("Failed to create message parameters with dictionary") + return + } + + XCTAssertEqual(parameters.parameterId, "admin") + XCTAssertEqual(parameters.name, "admin displayname") + XCTAssertEqual(parameters.mention?.idForChat, "@\"federated_user/admin@nextcloud.local\"") + } + +} diff --git a/NextcloudTalkTests/Unit/UnitMentionSuggestionTest.swift b/NextcloudTalkTests/Unit/UnitMentionSuggestionTest.swift index 5be0c51bd..1ad8017aa 100644 --- a/NextcloudTalkTests/Unit/UnitMentionSuggestionTest.swift +++ b/NextcloudTalkTests/Unit/UnitMentionSuggestionTest.swift @@ -17,11 +17,12 @@ final class UnitMentionSuggestionTest: XCTestCase { let suggestion = MentionSuggestion(dictionary: data) - XCTAssertEqual(suggestion.id, "my-id") - XCTAssertEqual(suggestion.label, "My Label") XCTAssertEqual(suggestion.source, "users") - XCTAssertEqual(suggestion.getIdForChat(), "my-id") - XCTAssertEqual(suggestion.getIdForAvatar(), "my-id") + + XCTAssertEqual(suggestion.mention.id, "my-id") + XCTAssertEqual(suggestion.mention.label, "My Label") + XCTAssertEqual(suggestion.mention.idForChat, "@\"my-id\"") + XCTAssertEqual(suggestion.mention.labelForChat, "@My Label") } func testLocalGuestMention() throws { @@ -31,9 +32,8 @@ final class UnitMentionSuggestionTest: XCTestCase { let suggestion = MentionSuggestion(dictionary: data) - XCTAssertEqual(suggestion.id, "guest/guest-id") - XCTAssertEqual(suggestion.getIdForChat(), "\"guest/guest-id\"") - XCTAssertEqual(suggestion.getIdForAvatar(), "guest/guest-id") + XCTAssertEqual(suggestion.mention.id, "guest/guest-id") + XCTAssertEqual(suggestion.mention.idForChat, "@\"guest/guest-id\"") } func testLocalWhitespaceMention() throws { @@ -43,9 +43,8 @@ final class UnitMentionSuggestionTest: XCTestCase { let suggestion = MentionSuggestion(dictionary: data) - XCTAssertEqual(suggestion.id, "my id") - XCTAssertEqual(suggestion.getIdForChat(), "\"my id\"") - XCTAssertEqual(suggestion.getIdForAvatar(), "my id") + XCTAssertEqual(suggestion.mention.id, "my id") + XCTAssertEqual(suggestion.mention.idForChat, "@\"my id\"") } func testMentionId() throws { @@ -56,10 +55,9 @@ final class UnitMentionSuggestionTest: XCTestCase { let suggestion = MentionSuggestion(dictionary: data) - XCTAssertEqual(suggestion.id, "my-id") - XCTAssertEqual(suggestion.mentionId, "mention-id") - XCTAssertEqual(suggestion.getIdForChat(), "mention-id") - XCTAssertEqual(suggestion.getIdForAvatar(), "my-id") + XCTAssertEqual(suggestion.mention.id, "my-id") + XCTAssertEqual(suggestion.mention.mentionId, "mention-id") + XCTAssertEqual(suggestion.mention.idForChat, "@\"mention-id\"") } func testMessageParameter() throws { @@ -75,8 +73,10 @@ final class UnitMentionSuggestionTest: XCTestCase { XCTAssertEqual(parameter.parameterId, "my-id") XCTAssertEqual(parameter.name, "My Label") - XCTAssertEqual(parameter.mentionDisplayName, "@My Label") - XCTAssertEqual(parameter.mentionId, "@mention-id") + XCTAssertEqual(parameter.mention?.label, "My Label") + XCTAssertEqual(parameter.mention?.mentionId, "mention-id") + XCTAssertEqual(parameter.mention?.idForChat, "@\"mention-id\"") + XCTAssertEqual(parameter.mention?.labelForChat, "@My Label") XCTAssertEqual(parameter.type, "user") } }