From a1d40a5933a82156fe2bbbc41b275e95f2c037ed Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 8 Oct 2019 15:02:03 +1100 Subject: [PATCH] Implement mention rendering --- .../Cells/OWSMessageBubbleView.m | 52 ++++++++++++++----- SignalServiceKit/src/Loki/API/LokiAPI.swift | 15 +++++- .../src/Messages/OWSMessageManager.m | 5 ++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 53f6ea773..d80c21ebb 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -676,12 +676,14 @@ NS_ASSUME_NONNULL_BEGIN TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; shouldIgnoreEvents = outgoingMessage.messageState != TSOutgoingMessageStateSent; } + NSString *threadID = self.viewItem.interaction.uniqueThreadId; [self.class loadForTextDisplay:self.bodyTextView displayableText:self.displayableBodyText searchText:self.delegate.lastSearchedText textColor:self.bodyTextColor font:self.textMessageFont - shouldIgnoreEvents:shouldIgnoreEvents]; + shouldIgnoreEvents:shouldIgnoreEvents + threadID:threadID]; } + (void)loadForTextDisplay:(OWSMessageTextView *)textView @@ -690,11 +692,11 @@ NS_ASSUME_NONNULL_BEGIN textColor:(UIColor *)textColor font:(UIFont *)font shouldIgnoreEvents:(BOOL)shouldIgnoreEvents + threadID:(NSString *)threadID { textView.hidden = NO; textView.textColor = textColor; - // Honor dynamic type in the message bodies. textView.font = font; textView.linkTextAttributes = @{ NSForegroundColorAttributeName : textColor, @@ -703,21 +705,43 @@ NS_ASSUME_NONNULL_BEGIN textView.shouldIgnoreEvents = shouldIgnoreEvents; NSString *text = displayableText.displayText; - - NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] - initWithString:text - attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }]; + + NSError *error1; + NSRegularExpression *regex1 = [[NSRegularExpression alloc] initWithPattern:@"@\\w*" options:0 error:&error1]; + OWSAssertDebug(error1 == nil); + NSSet *knownUserIDs = LKAPI.userHexEncodedPublicKeyCache[threadID]; + NSMutableSet *mentions = [NSMutableSet new]; + NSTextCheckingResult *match = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]; + if (match != nil) { + while (YES) { + NSString *userID = [[text substringWithRange:match.range] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""]; + NSUInteger matchEnd; + if ([knownUserIDs containsObject:userID]) { + NSString *userDisplayName = [Environment.shared.contactsManager attributedContactOrProfileNameForPhoneIdentifier:userID primaryFont:font secondaryFont:font].string; + text = [text stringByReplacingCharactersInRange:match.range withString:[NSString stringWithFormat:@"@%@", userDisplayName]]; + [mentions addObject:[NSValue valueWithRange:NSMakeRange(match.range.location, userDisplayName.length + 1)]]; + matchEnd = match.range.location + userDisplayName.length; + } else { + matchEnd = match.range.location + match.range.length; + } + match = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(matchEnd, text.length - matchEnd)]; + if (match == nil) { break; } + } + } + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }]; + for (NSValue *mention in mentions) { + NSRange range = mention.rangeValue; + [attributedText addAttribute:NSBackgroundColorAttributeName value:UIColor.lokiDarkGray range:range]; + [attributedText addAttribute:NSForegroundColorAttributeName value:UIColor.ows_whiteColor range:range]; + } + if (searchText.length >= ConversationSearchController.kMinimumSearchTextLength) { NSString *searchableText = [FullTextSearchFinder normalizeWithText:searchText]; - NSError *error; - NSRegularExpression *regex = - [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] - options:NSRegularExpressionCaseInsensitive - error:&error]; - OWSAssertDebug(error == nil); + NSError *error2; + NSRegularExpression *regex2 = [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] options:NSRegularExpressionCaseInsensitive error:&error2]; + OWSAssertDebug(error2 == nil); for (NSTextCheckingResult *match in - [regex matchesInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]) { - + [regex2 matchesInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]) { OWSAssertDebug(match.range.length >= ConversationSearchController.kMinimumSearchTextLength); [attributedText addAttribute:NSBackgroundColorAttributeName value:UIColor.yellowColor range:match.range]; [attributedText addAttribute:NSForegroundColorAttributeName value:UIColor.ows_blackColor range:match.range]; diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index f84609aa0..217a1df13 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -260,7 +260,7 @@ public final class LokiAPI : NSObject { } } - // MARK: Caching + // MARK: Message Caching private static let receivedMessageHashValuesKey = "receivedMessageHashValuesKey" private static let receivedMessageHashValuesCollection = "receivedMessageHashValuesCollection" @@ -293,6 +293,19 @@ public final class LokiAPI : NSObject { transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection) } } + + // MARK: User ID Caching + @objc static var userHexEncodedPublicKeyCache: [String:Set] = [:] // Thread ID to set of user hex encoded public keys + + @objc public static func cache(_ userHexEncodedPublicKey: String, for thread: String) { + if let cache = userHexEncodedPublicKeyCache[thread] { + var mutableCache = cache + mutableCache.insert(userHexEncodedPublicKey) + userHexEncodedPublicKeyCache[thread] = mutableCache + } else { + userHexEncodedPublicKeyCache[thread] = [ userHexEncodedPublicKey ] + } + } } // MARK: Error Handling diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 7f84b2ab7..81f8965dd 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1413,15 +1413,20 @@ NS_ASSUME_NONNULL_BEGIN return nil; } + // Loki: Cache the user hex encoded public key (for mentions) + [LKAPI cache:incomingMessage.authorId for:oldGroupThread.uniqueId]; + [self finalizeIncomingMessage:incomingMessage thread:oldGroupThread envelope:envelope transaction:transaction]; + // Loki: Map the message ID to the message server ID if needed if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { [self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction]; } + // Loki: Generate a link preview if needed dispatch_async(dispatch_get_main_queue(), ^{ NSString *linkPreviewURL = [OWSLinkPreview previewURLForRawBodyText:incomingMessage.body]; if (linkPreviewURL != nil) {