session-ios/Session/Utilities/MentionUtilities.swift

108 lines
4.5 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUIKit
import SessionMessagingKit
public enum MentionUtilities {
public static func highlightMentions(
in string: String,
threadVariant: SessionThread.Variant,
currentUserPublicKey: String,
currentUserBlindedPublicKey: String?
) -> String {
return highlightMentions(
in: string,
threadVariant: threadVariant,
currentUserPublicKey: currentUserPublicKey,
currentUserBlindedPublicKey: currentUserBlindedPublicKey,
isOutgoingMessage: false,
attributes: [:]
).string // isOutgoingMessage and attributes are irrelevant
}
public static func highlightMentions(
in string: String,
threadVariant: SessionThread.Variant,
currentUserPublicKey: String?,
currentUserBlindedPublicKey: String?,
isOutgoingMessage: Bool,
attributes: [NSAttributedString.Key: Any]
) -> NSAttributedString {
guard
let regex: NSRegularExpression = try? NSRegularExpression(pattern: "@[0-9a-fA-F]{66}", options: [])
else {
return NSAttributedString(string: string)
}
var string = string
var lastMatchEnd: Int = 0
var mentions: [(range: NSRange, isCurrentUser: Bool)] = []
let currentUserPublicKeys: Set<String> = [
currentUserPublicKey,
currentUserBlindedPublicKey
]
.compactMap { $0 }
.asSet()
while let match: NSTextCheckingResult = regex.firstMatch(
in: string,
options: .withoutAnchoringBounds,
range: NSRange(location: lastMatchEnd, length: string.utf16.count - lastMatchEnd)
) {
guard let range: Range = Range(match.range, in: string) else { break }
let publicKey: String = String(string[range].dropFirst()) // Drop the @
let isCurrentUser: Bool = currentUserPublicKeys.contains(publicKey)
guard let targetString: String = {
guard !isCurrentUser else { return "MEDIA_GALLERY_SENDER_NAME_YOU".localized() }
guard let displayName: String = Profile.displayNameNoFallback(id: publicKey, threadVariant: threadVariant) else {
lastMatchEnd = (match.range.location + match.range.length)
return nil
}
return displayName
}()
else { continue }
string = string.replacingCharacters(in: range, with: "@\(targetString)")
lastMatchEnd = (match.range.location + targetString.utf16.count)
mentions.append((
// + 1 to include the @
range: NSRange(location: match.range.location, length: targetString.utf16.count + 1),
isCurrentUser: isCurrentUser
))
}
let sizeDiff: CGFloat = (Values.smallFontSize / Values.mediumFontSize)
let result: NSMutableAttributedString = NSMutableAttributedString(string: string, attributes: attributes)
mentions.forEach { mention in
result.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.smallFontSize), range: mention.range)
if mention.isCurrentUser {
// Note: The designs don't match with the dynamic sizing so these values need to be calculated
// to maintain a "rounded rect" effect rather than a "pill" effect
result.addAttribute(.currentUserMentionBackgroundCornerRadius, value: (8 * sizeDiff), range: mention.range)
result.addAttribute(.currentUserMentionBackgroundPadding, value: (3 * sizeDiff), range: mention.range)
result.addAttribute(.currentUserMentionBackgroundColor, value: Colors.accent, range: mention.range)
result.addAttribute(.foregroundColor, value: UIColor.black, range: mention.range)
}
else {
let color: UIColor = {
switch (isLightMode, isOutgoingMessage) {
case (_, true): return .black
case (true, false): return .black
case (false, false): return Colors.accent
}
}()
result.addAttribute(.foregroundColor, value: color, range: mention.range)
}
}
return result
}
}