mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Render mentions in previews & refactor
This commit is contained in:
parent
bd62ad099d
commit
8344a86412
|
@ -568,6 +568,7 @@
|
|||
B845B4D4230CD09100D759F0 /* GroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */; };
|
||||
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
|
||||
B84664F3234FE4540083A1CD /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F2234FE4530083A1CD /* Mention.swift */; };
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
|
||||
B86BD08123399883000F5AE3 /* QRCodeModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08023399883000F5AE3 /* QRCodeModal.swift */; };
|
||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
||||
|
@ -1380,6 +1381,7 @@
|
|||
B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatPoller.swift; sourceTree = "<group>"; };
|
||||
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
|
||||
B84664F2234FE4530083A1CD /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
||||
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
|
||||
B86BD08023399883000F5AE3 /* QRCodeModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeModal.swift; sourceTree = "<group>"; };
|
||||
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
|
||||
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
||||
|
@ -2701,6 +2703,7 @@
|
|||
children = (
|
||||
B86BD08323399ACF000F5AE3 /* Modal.swift */,
|
||||
B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */,
|
||||
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3882,6 +3885,7 @@
|
|||
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
|
||||
B84664F3234FE4540083A1CD /* Mention.swift in Sources */,
|
||||
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
|
||||
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
|
||||
|
|
49
Signal/src/Loki/Utilities/MentionUtilities.swift
Normal file
49
Signal/src/Loki/Utilities/MentionUtilities.swift
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
@objc(LKMentionUtilities)
|
||||
public final class MentionUtilities : NSObject {
|
||||
|
||||
override private init() { }
|
||||
|
||||
@objc public static func highlightMentions(in string: String, thread: TSThread) -> String {
|
||||
return highlightMentions(in: string, isOutgoingMessage: false, thread: thread, attributes: [:]).string // isOutgoingMessage and attributes are irrelevant
|
||||
}
|
||||
|
||||
@objc public static func highlightMentions(in string: String, isOutgoingMessage: Bool, thread: TSThread, attributes: [NSAttributedString.Key:Any]) -> NSAttributedString {
|
||||
var string = string
|
||||
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]*", options: [])
|
||||
let knownUserIDs = LokiAPI.userIDCache[thread.uniqueId!] ?? [] // Should always be populated at this point
|
||||
var mentions: [NSRange] = []
|
||||
var outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.count))
|
||||
while let match = outerMatch, thread.isGroupThread() {
|
||||
let userID = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @
|
||||
let matchEnd: Int
|
||||
if knownUserIDs.contains(userID) {
|
||||
var userDisplayName: String?
|
||||
if userID == OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey {
|
||||
userDisplayName = OWSProfileManager.shared().localProfileName()
|
||||
} else {
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
let collection = "\(LokiGroupChatAPI.publicChatServer).\(LokiGroupChatAPI.publicChatServerID)"
|
||||
userDisplayName = transaction.object(forKey: userID, inCollection: collection) as! String?
|
||||
}
|
||||
}
|
||||
if let userDisplayName = userDisplayName {
|
||||
string = (string as NSString).replacingCharacters(in: match.range, with: "@\(userDisplayName)")
|
||||
mentions.append(NSRange(location: match.range.location, length: userDisplayName.count + 1)) // + 1 to include the @
|
||||
matchEnd = match.range.location + userDisplayName.count
|
||||
} else {
|
||||
matchEnd = match.range.location + match.range.length
|
||||
}
|
||||
} else {
|
||||
matchEnd = match.range.location + match.range.length
|
||||
}
|
||||
outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: matchEnd, length: string.count - matchEnd))
|
||||
}
|
||||
let result = NSMutableAttributedString(string: string, attributes: attributes)
|
||||
mentions.forEach { mention in
|
||||
let color: UIColor = isOutgoingMessage ? .lokiDarkGray() : .lokiGreen()
|
||||
result.addAttribute(.backgroundColor, value: color, range: mention)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@
|
|||
#import "UIColor+OWS.h"
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
// TODO: Reduce code duplication around mention detection
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareButtonsViewDelegate>
|
||||
|
@ -685,7 +683,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
font:self.textMessageFont
|
||||
shouldIgnoreEvents:shouldIgnoreEvents
|
||||
thread:self.viewItem.interaction.thread
|
||||
isOutgoing:[self.viewItem.interaction isKindOfClass:TSOutgoingMessage.self]];
|
||||
isOutgoingMessage:[self.viewItem.interaction isKindOfClass:TSOutgoingMessage.self]];
|
||||
}
|
||||
|
||||
+ (void)loadForTextDisplay:(OWSMessageTextView *)textView
|
||||
|
@ -695,7 +693,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
font:(UIFont *)font
|
||||
shouldIgnoreEvents:(BOOL)shouldIgnoreEvents
|
||||
thread:(TSThread *)thread
|
||||
isOutgoing:(BOOL)isOutgoing
|
||||
isOutgoingMessage:(BOOL)isOutgoingMessage
|
||||
{
|
||||
textView.hidden = NO;
|
||||
textView.textColor = textColor;
|
||||
|
@ -709,57 +707,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
NSString *text = displayableText.displayText;
|
||||
|
||||
NSError *error1;
|
||||
NSRegularExpression *regex1 = [[NSRegularExpression alloc] initWithPattern:@"@[0-9a-fA-F]*" options:0 error:&error1];
|
||||
OWSAssertDebug(error1 == nil);
|
||||
NSSet<NSString *> *knownUserIDs = LKAPI.userIDCache[thread.uniqueId];
|
||||
NSMutableArray<NSValue *> *mentions = [NSMutableArray new];
|
||||
NSTextCheckingResult *match1 = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)];
|
||||
if (match1 != nil && thread.isGroupThread) {
|
||||
while (YES) {
|
||||
NSString *userID = [[text substringWithRange:match1.range] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
|
||||
NSUInteger matchEnd;
|
||||
if ([knownUserIDs containsObject:userID]) {
|
||||
__block NSString *userDisplayName;
|
||||
if ([userID isEqual:OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey]) {
|
||||
userDisplayName = OWSProfileManager.sharedManager.localProfileName;
|
||||
} else {
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSString *collection = [NSString stringWithFormat:@"%@.%llu", LKGroupChatAPI.publicChatServer, LKGroupChatAPI.publicChatServerID];
|
||||
userDisplayName = [transaction objectForKey:userID inCollection:collection];
|
||||
}];
|
||||
}
|
||||
if (userDisplayName != nil) {
|
||||
text = [text stringByReplacingCharactersInRange:match1.range withString:[NSString stringWithFormat:@"@%@", userDisplayName]];
|
||||
[mentions addObject:[NSValue valueWithRange:NSMakeRange(match1.range.location, userDisplayName.length + 1)]];
|
||||
matchEnd = match1.range.location + userDisplayName.length;
|
||||
} else {
|
||||
matchEnd = match1.range.location + match1.range.length;
|
||||
}
|
||||
} else {
|
||||
matchEnd = match1.range.location + match1.range.length;
|
||||
}
|
||||
match1 = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(matchEnd, text.length - matchEnd)];
|
||||
if (match1 == nil) { break; }
|
||||
}
|
||||
}
|
||||
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }];
|
||||
for (NSValue *mention in mentions) {
|
||||
NSRange range = mention.rangeValue;
|
||||
UIColor *highlightColor = isOutgoing ? UIColor.lokiDarkGray : UIColor.lokiGreen;
|
||||
[attributedText addAttribute:NSBackgroundColorAttributeName value:highlightColor range:range];
|
||||
}
|
||||
NSMutableAttributedString *attributedText = [LKMentionUtilities highlightMentionsIn:text isOutgoingMessage:isOutgoingMessage thread:thread attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }].mutableCopy;
|
||||
|
||||
if (searchText.length >= ConversationSearchController.kMinimumSearchTextLength) {
|
||||
NSString *searchableText = [FullTextSearchFinder normalizeWithText:searchText];
|
||||
NSError *error2;
|
||||
NSRegularExpression *regex2 = [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] options:NSRegularExpressionCaseInsensitive error:&error2];
|
||||
OWSAssertDebug(error2 == nil);
|
||||
for (NSTextCheckingResult *match2 in
|
||||
[regex2 matchesInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]) {
|
||||
OWSAssertDebug(match2.range.length >= ConversationSearchController.kMinimumSearchTextLength);
|
||||
[attributedText addAttribute:NSBackgroundColorAttributeName value:UIColor.yellowColor range:match2.range];
|
||||
[attributedText addAttribute:NSForegroundColorAttributeName value:UIColor.ows_blackColor range:match2.range];
|
||||
NSError *error;
|
||||
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] options:NSRegularExpressionCaseInsensitive error:&error];
|
||||
OWSAssertDebug(error == nil);
|
||||
for (NSTextCheckingResult *match in
|
||||
[regex 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1197,40 +1156,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (DisplayableText *)getDisplayableQuotedText
|
||||
{
|
||||
if (!self.viewItem.hasQuotedText) { return nil; }
|
||||
NSString *text = self.viewItem.displayableQuotedText.fullText;
|
||||
NSString *rawText = self.viewItem.displayableQuotedText.fullText;
|
||||
TSThread *thread = self.viewItem.interaction.thread;
|
||||
NSError *error;
|
||||
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:@"@[0-9a-fA-F]*" options:0 error:&error];
|
||||
OWSAssertDebug(error == nil);
|
||||
NSSet<NSString *> *knownUserIDs = LKAPI.userIDCache[thread.uniqueId];
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)];
|
||||
if (match != nil && thread.isGroupThread) {
|
||||
while (YES) {
|
||||
NSString *userID = [[text substringWithRange:match.range] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
|
||||
NSUInteger matchEnd;
|
||||
if ([knownUserIDs containsObject:userID]) {
|
||||
__block NSString *userDisplayName;
|
||||
if ([userID isEqual:OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey]) {
|
||||
userDisplayName = OWSProfileManager.sharedManager.localProfileName;
|
||||
} else {
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSString *collection = [NSString stringWithFormat:@"%@.%llu", LKGroupChatAPI.publicChatServer, LKGroupChatAPI.publicChatServerID];
|
||||
userDisplayName = [transaction objectForKey:userID inCollection:collection];
|
||||
}];
|
||||
}
|
||||
if (userDisplayName != nil) {
|
||||
text = [text stringByReplacingCharactersInRange:match.range withString:[NSString stringWithFormat:@"@%@", userDisplayName]];
|
||||
matchEnd = match.range.location + userDisplayName.length;
|
||||
} else {
|
||||
matchEnd = match.range.location + match.range.length;
|
||||
}
|
||||
} else {
|
||||
matchEnd = match.range.location + match.range.length;
|
||||
}
|
||||
match = [regex firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(matchEnd, text.length - matchEnd)];
|
||||
if (match == nil) { break; }
|
||||
}
|
||||
}
|
||||
NSString *text = [LKMentionUtilities highlightMentionsIn:rawText thread:thread];
|
||||
return [DisplayableText displayableText:text];
|
||||
}
|
||||
|
||||
|
|
|
@ -395,6 +395,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
NSString *displayableText = thread.lastMessageText;
|
||||
if (displayableText) {
|
||||
[LKAPI populateUserIDCacheIfNeededFor:thread.threadRecord.uniqueId in:nil]; // TODO: Terrible place to do this, but okay for now
|
||||
displayableText = [LKMentionUtilities highlightMentionsIn:displayableText thread:thread.threadRecord];
|
||||
[snippetText appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:displayableText
|
||||
attributes:@{
|
||||
|
|
|
@ -3,7 +3,7 @@ import PromiseKit
|
|||
@objc(LKAPI)
|
||||
public final class LokiAPI : NSObject {
|
||||
private static var lastDeviceLinkUpdate: [String:Date] = [:] // Hex encoded public key to date
|
||||
@objc static var userIDCache: [String:Set<String>] = [:] // Thread ID to set of user hex encoded public keys
|
||||
@objc public static var userIDCache: [String:Set<String>] = [:] // Thread ID to set of user hex encoded public keys
|
||||
|
||||
// MARK: Convenience
|
||||
internal static let storage = OWSPrimaryStorage.shared()
|
||||
|
|
Loading…
Reference in a new issue