NCRoom.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. //
  2. // SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  3. // SPDX-License-Identifier: GPL-3.0-or-later
  4. //
  5. import Foundation
  6. import Realm
  7. @objc extension NCRoom {
  8. public static func stringFor(notificationLevel level: NCRoomNotificationLevel) -> String {
  9. var levelString = ""
  10. switch level {
  11. case .always:
  12. levelString = NSLocalizedString("All messages", comment: "")
  13. case .mention:
  14. levelString = NSLocalizedString("@-mentions only", comment: "")
  15. case .never:
  16. levelString = NSLocalizedString("Off", comment: "")
  17. default:
  18. levelString = NSLocalizedString("Default", comment: "")
  19. }
  20. return levelString
  21. }
  22. public static func stringFor(messageExpiration expiration: NCMessageExpiration) -> String {
  23. var levelString = ""
  24. switch expiration {
  25. case .expiration4Weeks:
  26. levelString = NSLocalizedString("4 weeks", comment: "")
  27. case .expiration1Week:
  28. levelString = NSLocalizedString("1 week", comment: "")
  29. case .expiration1Day:
  30. levelString = NSLocalizedString("1 day", comment: "")
  31. case .expiration8Hours:
  32. levelString = NSLocalizedString("8 hours", comment: "")
  33. case .expiration1Hour:
  34. levelString = NSLocalizedString("1 hour", comment: "")
  35. default:
  36. levelString = NSLocalizedString("Off", comment: "")
  37. }
  38. return levelString
  39. }
  40. public var isPublic: Bool {
  41. return self.type == .public
  42. }
  43. public var isFederated: Bool {
  44. if let remoteToken, let remoteServer {
  45. return !remoteToken.isEmpty && !remoteServer.isEmpty
  46. }
  47. return false
  48. }
  49. public var supportsFederatedCalling: Bool {
  50. guard self.isFederated else { return false }
  51. let remoteCapabilitySupported = NCDatabaseManager.sharedInstance().roomHasTalkCapability(kCapabilityFederationV2, for: self)
  52. let localCapabilitySupported = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityFederationV2, forAccountId: self.accountId)
  53. let remoteCallingEnabled = NCDatabaseManager.sharedInstance().roomTalkCapabilities(for: self)?.callEnabled ?? false
  54. let localCallingEnabled = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: self.accountId)?.callEnabled ?? false
  55. let capabilitySupported = remoteCapabilitySupported && localCapabilitySupported
  56. let callingEnabled = remoteCallingEnabled && localCallingEnabled
  57. return capabilitySupported && callingEnabled
  58. }
  59. public var supportsCalling: Bool {
  60. if self.isFederated, !self.supportsFederatedCalling {
  61. return false
  62. }
  63. return NCDatabaseManager.sharedInstance().roomTalkCapabilities(for: self)?.callEnabled ?? false &&
  64. self.type != .changelog && self.type != .noteToSelf
  65. }
  66. public var supportsMessageExpirationModeration: Bool {
  67. if self.type == .formerOneToOne || self.type == .changelog {
  68. return false
  69. }
  70. return self.isUserOwnerOrModerator && NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityMessageExpiration)
  71. }
  72. public var supportsBanningModeration: Bool {
  73. let supportedType = self.type == .group || self.type == .public
  74. return supportedType && self.canModerate && NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityBanV1)
  75. }
  76. public var isBreakoutRoom: Bool {
  77. return self.objectType == NCRoomObjectTypeRoom
  78. }
  79. public var isUserOwnerOrModerator: Bool {
  80. return self.participantType == .owner || self.participantType == .moderator
  81. }
  82. public var canModerate: Bool {
  83. return self.isUserOwnerOrModerator && !self.isLockedOneToOne
  84. }
  85. public var isNameEditable: Bool {
  86. return self.canModerate && self.type != .oneToOne && self.type != .formerOneToOne
  87. }
  88. private var isLockedOneToOne: Bool {
  89. let lockedOneToOne = self.type == .oneToOne && NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityLockedOneToOneRooms)
  90. let lockedOther = self.type == .formerOneToOne || self.type == .noteToSelf
  91. return lockedOneToOne || lockedOther
  92. }
  93. public var isLeavable: Bool {
  94. // Allow users to leave when there are no moderators in the room
  95. // (No need to check room type because in one2one rooms users will always be moderators)
  96. // or when in a group call and there are other participants.
  97. // We can also check "canLeaveConversation" since v2
  98. if self.type != .oneToOne && self.type != .formerOneToOne && self.participants.count > 1 {
  99. return true
  100. }
  101. return self.canLeaveConversation || self.canModerate
  102. }
  103. public var userCanStartCall: Bool {
  104. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityStartCallFlag) && !self.canStartCall {
  105. return false
  106. }
  107. return true
  108. }
  109. public var hasUnreadMention: Bool {
  110. if self.type == .oneToOne || self.type == .formerOneToOne {
  111. return self.unreadMessages > 0
  112. }
  113. return self.unreadMention || self.unreadMentionDirect
  114. }
  115. public var callRecordingIsInActiveState: Bool {
  116. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityRecordingV1) {
  117. // Starting states and running states are considered active
  118. if self.callRecording != NCCallRecordingState.stopped.rawValue && self.callRecording != NCCallRecordingState.failed.rawValue {
  119. return true
  120. }
  121. }
  122. return false
  123. }
  124. public var deletionMessage: String {
  125. var message = NSLocalizedString("Do you really want to delete this conversation?", comment: "")
  126. if self.type == .oneToOne || self.type == .formerOneToOne {
  127. message = String(format: NSLocalizedString("If you delete the conversation, it will also be deleted for %@", comment: ""), self.displayName)
  128. } else if self.participants.count > 1 {
  129. message = NSLocalizedString("If you delete the conversation, it will also be deleted for all other participants.", comment: "")
  130. }
  131. return message
  132. }
  133. public var notificationLevelString: String {
  134. return NCRoom.stringFor(notificationLevel: self.notificationLevel)
  135. }
  136. public var messageExpirationString: String {
  137. if let tempMessageExpiration = NCMessageExpiration(rawValue: self.messageExpiration) {
  138. return NCRoom.stringFor(messageExpiration: tempMessageExpiration)
  139. }
  140. return "\(self.messageExpiration)s"
  141. }
  142. public var lastMessageString: NSMutableAttributedString? {
  143. var lastMessage = self.lastMessage
  144. if self.isFederated && lastMessage == nil {
  145. let lastMessageDictionary = self.lastMessageProxiedDictionary
  146. lastMessage = NCChatMessage(dictionary: lastMessageDictionary)
  147. }
  148. guard let lastMessage,
  149. let account = NCDatabaseManager.sharedInstance().talkAccount(forAccountId: self.accountId)
  150. else { return nil }
  151. let ownMessage = lastMessage.actorId == account.userId
  152. var actorName = lastMessage.actorDisplayName.components(separatedBy: " ").first ?? ""
  153. // For own messages
  154. if ownMessage {
  155. actorName = NSLocalizedString("You", comment: "")
  156. }
  157. // For guests
  158. if lastMessage.actorDisplayName.isEmpty {
  159. actorName = NSLocalizedString("Guest", comment: "")
  160. }
  161. // No actor name cases
  162. if lastMessage.isSystemMessage || (self.type == .oneToOne && !ownMessage) {
  163. actorName = ""
  164. }
  165. // Add separator
  166. if !actorName.isEmpty {
  167. actorName = "\(actorName): "
  168. }
  169. let lastMessageString = NSMutableAttributedString(string: actorName)
  170. if let messageIconName = lastMessage.messageIconName, let messageIcon = UIImage(systemName: messageIconName) {
  171. let attachmentString = NSMutableAttributedString(attachment: NSTextAttachment(image: messageIcon))
  172. attachmentString.append(NSAttributedString(string: " "))
  173. lastMessageString.append(attachmentString)
  174. }
  175. let parsedMarkdownString = String(lastMessage.parsedMarkdown().string.prefix(80))
  176. lastMessageString.append(NSAttributedString(string: parsedMarkdownString))
  177. return lastMessageString.withFont(.preferredFont(forTextStyle: .callout)).withTextColor(.secondaryLabel)
  178. }
  179. private var lastMessageProxiedDictionary: [AnyHashable: Any] {
  180. guard let data = self.lastMessageProxiedJSONString.data(using: .utf8),
  181. let jsonData = try? JSONSerialization.jsonObject(with: data) as? [AnyHashable: Any]
  182. else { return [:] }
  183. return jsonData
  184. }
  185. public var lastMessage: NCChatMessage? {
  186. guard let lastMessageId else { return nil }
  187. var unmanagedChatMessage: NCChatMessage?
  188. if let managedChatMessage = NCChatMessage.objects(where: "internalId = %@", lastMessageId).firstObject() {
  189. unmanagedChatMessage = NCChatMessage(value: managedChatMessage)
  190. }
  191. return unmanagedChatMessage
  192. }
  193. public var linkURL: String? {
  194. guard let account = NCDatabaseManager.sharedInstance().talkAccount(forAccountId: self.accountId),
  195. let serverCapabilities = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: self.accountId),
  196. let token = self.token
  197. else { return nil }
  198. var indexString = "/index.php"
  199. if serverCapabilities.modRewriteWorking {
  200. indexString = ""
  201. }
  202. return "\(account.server)\(indexString)/call/\(token)"
  203. }
  204. }