Merge branch 'dev' into custom-server

This commit is contained in:
Niels Andriesse 2019-10-14 12:55:11 +11:00
commit 86550c8877
18 changed files with 262 additions and 184 deletions

2
Pods

@ -1 +1 @@
Subproject commit 68a1e49959447a8ef4b4de77edec53375c598268
Subproject commit 56980ceea7cc0964b92f98831e79fea03e76e801

View File

@ -567,6 +567,7 @@
B8258493230FA5E9001B41CB /* ScanQRCodeVC.m in Sources */ = {isa = PBXBuildFile; fileRef = B8258492230FA5E9001B41CB /* ScanQRCodeVC.m */; };
B82584A02315024B001B41CB /* RSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* RSSFeedPoller.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.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 */; };
@ -578,8 +579,8 @@
B894D0712339D6F300B4D94D /* DeviceLinkingModalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */; };
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; };
B89841E322B7579F00B1BDC6 /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */; };
B8B26C8F234D629C004ED98C /* UserSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C8E234D629C004ED98C /* UserSelectionView.swift */; };
B8B26C91234D8CBD004ED98C /* UserSelectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C90234D8CBD004ED98C /* UserSelectionViewDelegate.swift */; };
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */; };
B8B26C91234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
@ -1378,6 +1379,7 @@
B8258492230FA5E9001B41CB /* ScanQRCodeVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScanQRCodeVC.m; sourceTree = "<group>"; };
B825849F2315024B001B41CB /* RSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSFeedPoller.swift; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.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>"; };
@ -1387,8 +1389,8 @@
B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModalDelegate.swift; sourceTree = "<group>"; };
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = "<group>"; };
B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = "<group>"; };
B8B26C8E234D629C004ED98C /* UserSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionView.swift; sourceTree = "<group>"; };
B8B26C90234D8CBD004ED98C /* UserSelectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionViewDelegate.swift; sourceTree = "<group>"; };
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionCandidateSelectionView.swift; sourceTree = "<group>"; };
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionCandidateSelectionViewDelegate.swift; sourceTree = "<group>"; };
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
@ -2656,8 +2658,8 @@
B8258491230FA5DA001B41CB /* ScanQRCodeVC.h */,
B8258492230FA5E9001B41CB /* ScanQRCodeVC.m */,
24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */,
B8B26C8E234D629C004ED98C /* UserSelectionView.swift */,
B8B26C90234D8CBD004ED98C /* UserSelectionViewDelegate.swift */,
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
);
path = Loki;
sourceTree = "<group>";
@ -2699,6 +2701,7 @@
children = (
B86BD08323399ACF000F5AE3 /* Modal.swift */,
B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */,
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
);
path = Utilities;
sourceTree = "<group>";
@ -3845,7 +3848,7 @@
4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */,
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */,
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
B8B26C8F234D629C004ED98C /* UserSelectionView.swift in Sources */,
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */,
@ -3869,7 +3872,7 @@
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */,
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */,
B8B26C91234D8CBD004ED98C /* UserSelectionViewDelegate.swift in Sources */,
B8B26C91234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift in Sources */,
340FC8BB204DAC8D007AEB0F /* OWSAddToContactViewController.m in Sources */,
45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */,
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */,
@ -3879,6 +3882,7 @@
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,

View File

@ -1,21 +1,21 @@
// MARK: - User Selection View
@objc(LKUserSelectionView)
final class UserSelectionView : UIView, UITableViewDataSource, UITableViewDelegate {
@objc var users: [String] = [] { didSet { tableView.reloadData() } }
@objc(LKMentionCandidateSelectionView)
final class MentionCandidateSelectionView : UIView, UITableViewDataSource, UITableViewDelegate {
@objc var mentionCandidates: [Mention] = [] { didSet { tableView.reloadData() } }
@objc var hasGroupContext = false
@objc var delegate: UserSelectionViewDelegate?
@objc var delegate: MentionCandidateSelectionViewDelegate?
// MARK: Components
private lazy var tableView: UITableView = {
@objc lazy var tableView: UITableView = { // TODO: Make this private
let result = UITableView()
result.dataSource = self
result.delegate = self
result.register(Cell.self, forCellReuseIdentifier: "Cell")
result.separatorStyle = .none
result.backgroundColor = .clear
result.contentInset = UIEdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)
result.contentInset = UIEdgeInsets(top: 6, leading: 0, bottom: 0, trailing: 0)
return result
}()
@ -37,30 +37,30 @@ final class UserSelectionView : UIView, UITableViewDataSource, UITableViewDelega
// MARK: Data
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
return mentionCandidates.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
let user = users[indexPath.row]
cell.user = user
let mentionCandidate = mentionCandidates[indexPath.row]
cell.mentionCandidate = mentionCandidate
cell.hasGroupContext = hasGroupContext
return cell
}
// MARK: Interaction
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user = users[indexPath.row]
delegate?.handleUserSelected(user, from: self)
let mentionCandidate = mentionCandidates[indexPath.row]
delegate?.handleMentionCandidateSelected(mentionCandidate, from: self)
}
}
// MARK: - Cell
private extension UserSelectionView {
private extension MentionCandidateSelectionView {
final class Cell : UITableViewCell {
var user = "" { didSet { update() } }
var mentionCandidate = Mention(hexEncodedPublicKey: "", displayName: "") { didSet { update() } }
var hasGroupContext = false
// MARK: Components
@ -101,12 +101,12 @@ private extension UserSelectionView {
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 16
stackView.set(.height, to: 44)
stackView.set(.height, to: 36)
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: 16)
stackView.pin(.top, to: .top, of: contentView, withInset: 4)
stackView.pin(.top, to: .top, of: contentView, withInset: 8)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: 16)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: 4)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: 8)
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * 16)
// Set up the moderator icon image view
moderatorIconImageView.set(.width, to: 20)
@ -118,15 +118,10 @@ private extension UserSelectionView {
// MARK: Updating
private func update() {
var displayName: String = ""
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
let collection = "\(LokiGroupChatAPI.publicChatServer).\(LokiGroupChatAPI.publicChatServerID)"
displayName = transaction.object(forKey: self.user, inCollection: collection) as! String
}
displayNameLabel.text = displayName
let profilePicture = OWSContactAvatarBuilder(signalId: user, colorName: .blue, diameter: 36).build()
displayNameLabel.text = mentionCandidate.displayName
let profilePicture = OWSContactAvatarBuilder(signalId: mentionCandidate.hexEncodedPublicKey, colorName: .blue, diameter: 36).build()
profilePictureImageView.image = profilePicture
let isUserModerator = LokiGroupChatAPI.isUserModerator(user, for: LokiGroupChatAPI.publicChatServerID, on: LokiGroupChatAPI.publicChatServer)
let isUserModerator = LokiGroupChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, for: LokiGroupChatAPI.publicChatServerID, on: LokiGroupChatAPI.publicChatServer)
moderatorIconImageView.isHidden = !isUserModerator || !hasGroupContext
}
}

View File

@ -0,0 +1,6 @@
@objc(LKMentionCandidateSelectionViewDelegate)
protocol MentionCandidateSelectionViewDelegate {
func handleMentionCandidateSelected(_ mentionCandidate: Mention, from mentionCandidateSelectionView: MentionCandidateSelectionView)
}

View File

@ -6,7 +6,7 @@ final class DisplayNameVC : OnboardingBaseViewController {
result.textColor = Theme.primaryColor
result.font = UIFont.ows_dynamicTypeBodyClamped
result.textAlignment = .center
let placeholder = NSMutableAttributedString(string: NSLocalizedString("Display Name (Optional)", comment: ""))
let placeholder = NSMutableAttributedString(string: NSLocalizedString("Display Name", comment: ""))
placeholder.addAttribute(.foregroundColor, value: Theme.placeholderColor, range: NSRange(location: 0, length: placeholder.length))
result.attributedPlaceholder = placeholder
result.tintColor = UIColor.lokiGreen()
@ -14,11 +14,6 @@ final class DisplayNameVC : OnboardingBaseViewController {
result.keyboardAppearance = .dark
return result
}()
private var normalizedUserName: String? {
let result = userNameTextField.text!.ows_stripped()
return !result.isEmpty ? result : nil
}
override func viewDidLoad() {
super.viewDidLoad()
@ -59,16 +54,16 @@ final class DisplayNameVC : OnboardingBaseViewController {
}
@objc private func handleNextButtonPressed() {
if let normalizedName = normalizedUserName {
guard !OWSProfileManager.shared().isProfileNameTooLong(normalizedName) else {
return OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG", comment: "Error message shown when user tries to update profile with a profile name that is too long"))
}
let displayName = userNameTextField.text!.ows_stripped()
guard !displayName.isEmpty else {
return OWSAlerts.showErrorAlert(message: NSLocalizedString("Please pick a display name", comment: ""))
}
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else {
return OWSAlerts.showErrorAlert(message: NSLocalizedString("Please pick a shorter display name", comment: ""))
}
TSAccountManager.sharedInstance().didRegister()
UserDefaults.standard.set(true, forKey: "didUpdateForMainnet")
onboardingController.verificationDidComplete(fromView: self)
if let normalizedName = normalizedUserName {
OWSProfileManager.shared().updateLocalProfileName(normalizedName, avatarImage: nil, success: { }, failure: { }) // Try to save the user name but ignore the result
}
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: nil, success: { }, failure: { }) // Try to save the user name but ignore the result
}
}

View File

@ -1,6 +0,0 @@
@objc(LKUserSelectionViewDelegate)
protocol UserSelectionViewDelegate {
func handleUserSelected(_ user: String, from userSelectionView: UserSelectionView)
}

View 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 knownUserHexEncodedPublicKeys = LokiAPI.userHexEncodedPublicKeyCache[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 hexEncodedPublicKey = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @
let matchEnd: Int
if knownUserHexEncodedPublicKeys.contains(hexEncodedPublicKey) {
var userDisplayName: String?
if hexEncodedPublicKey == 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: hexEncodedPublicKey, 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
}
}

View File

@ -239,9 +239,8 @@ NS_ASSUME_NONNULL_BEGIN
[self.stackView addArrangedSubview:spacerView];
}
DisplayableText *_Nullable displayableQuotedText
= (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil);
DisplayableText *_Nullable displayableQuotedText = [self getDisplayableQuotedText];
OWSQuotedMessageView *quotedMessageView =
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText
@ -684,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
@ -694,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;
@ -708,57 +707,18 @@ NS_ASSUME_NONNULL_BEGIN
NSString *text = displayableText.displayText;
NSError *error1;
NSRegularExpression *regex1 = [[NSRegularExpression alloc] initWithPattern:@"@\\w*" 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];
}
}
@ -1181,9 +1141,8 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
DisplayableText *_Nullable displayableQuotedText
= (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil);
DisplayableText *_Nullable displayableQuotedText = [self getDisplayableQuotedText];
OWSQuotedMessageView *quotedMessageView =
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText
@ -1194,6 +1153,15 @@ NS_ASSUME_NONNULL_BEGIN
return [NSValue valueWithCGSize:CGSizeCeil(result)];
}
- (DisplayableText *)getDisplayableQuotedText
{
if (!self.viewItem.hasQuotedText) { return nil; }
NSString *rawText = self.viewItem.displayableQuotedText.fullText;
TSThread *thread = self.viewItem.interaction.thread;
NSString *text = [LKMentionUtilities highlightMentionsIn:rawText thread:thread];
return [DisplayableText displayableText:text];
}
- (nullable NSValue *)senderNameSize
{
OWSAssertDebug(self.conversationStyle);

View File

@ -5,7 +5,8 @@
NS_ASSUME_NONNULL_BEGIN
@class ConversationStyle;
@class LKUserSelectionView;
@class LKMention;
@class LKMentionCandidateSelectionView;
@class OWSLinkPreviewDraft;
@class OWSQuotedReplyModel;
@class SignalAttachment;
@ -29,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)voiceMemoGestureDidUpdateCancelWithRatioComplete:(CGFloat)cancelAlpha;
- (void)handleUserSelected:(NSString *)user from:(LKUserSelectionView *)userSelectionView;
- (void)handleMentionCandidateSelected:(LKMention *)mentionCandidate from:(LKMentionCandidateSelectionView *)mentionCandidateSelectionView;
@end
@ -84,11 +85,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)hideInputMethod;
#pragma mark - User Selection View
#pragma mark - Mention Candidate Selection View
- (void)showUserSelectionViewFor:(NSArray<NSString *> *)users in:(TSThread *)thread;
- (void)showMentionCandidateSelectionViewFor:(NSArray<LKMention *> *)mentionCandidates in:(TSThread *)thread;
- (void)hideUserSelectionView;
- (void)hideMentionCandidateSelectionView;
@end

View File

@ -51,7 +51,7 @@ const CGFloat kMaxTextViewHeight = 98;
@interface ConversationInputToolbar () <ConversationTextViewToolbarDelegate,
QuotedReplyPreviewDelegate,
LinkPreviewViewDraftDelegate,
LKUserSelectionViewDelegate>
LKMentionCandidateSelectionViewDelegate>
@property (nonatomic, readonly) ConversationStyle *conversationStyle;
@ -86,8 +86,8 @@ const CGFloat kMaxTextViewHeight = 98;
@property (nonatomic, nullable) InputLinkPreview *inputLinkPreview;
@property (nonatomic) BOOL wasLinkPreviewCancelled;
@property (nonatomic, nullable, weak) LinkPreviewView *linkPreviewView;
@property (nonatomic) LKUserSelectionView *userSelectionView;
@property (nonatomic) NSLayoutConstraint *userSelectionViewSizeConstraint;
@property (nonatomic) LKMentionCandidateSelectionView *mentionCandidateSelectionView;
@property (nonatomic) NSLayoutConstraint *mentionCandidateSelectionViewSizeConstraint;
@end
@ -223,12 +223,12 @@ const CGFloat kMaxTextViewHeight = 98;
[vStackWrapper setCompressionResistanceHorizontalLow];
// User Selection View
_userSelectionView = [LKUserSelectionView new];
[self addSubview:self.userSelectionView];
[self.userSelectionView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.userSelectionView autoPinWidthToSuperview];
self.userSelectionViewSizeConstraint = [self.userSelectionView autoSetDimension:ALDimensionHeight toSize:0];
self.userSelectionView.delegate = self;
_mentionCandidateSelectionView = [LKMentionCandidateSelectionView new];
[self addSubview:self.mentionCandidateSelectionView];
[self.mentionCandidateSelectionView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.mentionCandidateSelectionView autoPinWidthToSuperview];
self.mentionCandidateSelectionViewSizeConstraint = [self.mentionCandidateSelectionView autoSetDimension:ALDimensionHeight toSize:0];
self.mentionCandidateSelectionView.delegate = self;
// H Stack
_hStack = [[UIStackView alloc]
@ -240,7 +240,7 @@ const CGFloat kMaxTextViewHeight = 98;
self.hStack.spacing = 8;
[self addSubview:self.hStack];
[self.hStack autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.userSelectionView];
[self.hStack autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.mentionCandidateSelectionView];
[self.hStack autoPinEdgeToSuperviewSafeArea:ALEdgeBottom];
[self.hStack setContentHuggingHorizontalLow];
[self.hStack setCompressionResistanceHorizontalLow];
@ -1089,27 +1089,29 @@ const CGFloat kMaxTextViewHeight = 98;
self.borderView.hidden = YES;
}
#pragma mark - User Selection View
#pragma mark - Mention Candidate Selection View
- (void)showUserSelectionViewFor:(NSArray<NSString *> *)users in:(TSThread *)thread
- (void)showMentionCandidateSelectionViewFor:(NSArray<LKMention *> *)mentionCandidates in:(TSThread *)thread
{
self.userSelectionView.hasGroupContext = thread.isGroupThread; // Must happen before setting the users
self.userSelectionView.users = users;
self.userSelectionViewSizeConstraint.constant = 10 + MIN(users.count, 4) * 52;
self.mentionCandidateSelectionView.hasGroupContext = thread.isGroupThread; // Must happen before setting the users
self.mentionCandidateSelectionView.mentionCandidates = mentionCandidates;
self.mentionCandidateSelectionViewSizeConstraint.constant = 6 + MIN(mentionCandidates.count, 4) * 52;
[self setNeedsLayout];
[self layoutIfNeeded];
[self.mentionCandidateSelectionView.tableView setContentOffset:CGPointMake(0, -6)]; // TODO: Workaround for content offset bug
}
- (void)hideUserSelectionView
- (void)hideMentionCandidateSelectionView
{
self.userSelectionViewSizeConstraint.constant = 0;
self.mentionCandidateSelectionViewSizeConstraint.constant = 0;
[self setNeedsLayout];
[self layoutIfNeeded];
[self.mentionCandidateSelectionView.tableView setContentOffset:CGPointMake(0, 0)];
}
- (void)handleUserSelected:(NSString *)user from:(LKUserSelectionView *)userSelectionView
- (void)handleMentionCandidateSelected:(LKMention *)mentionCandidate from:(LKMentionCandidateSelectionView *)mentionCandidateSelectionView
{
[self.inputToolbarDelegate handleUserSelected:user from:userSelectionView];
[self.inputToolbarDelegate handleMentionCandidateSelected:mentionCandidate from:mentionCandidateSelectionView];
}
@end

View File

@ -213,7 +213,10 @@ typedef enum : NSUInteger {
@property (nonatomic) CGFloat extraContentInsetPadding;
@property (nonatomic) CGFloat contentInsetBottom;
@property (nonatomic) NSInteger mentionStartIndex;
// Mentions
@property (nonatomic) NSInteger currentMentionStartIndex;
@property (nonatomic) NSMutableArray<LKMention *> *mentions;
@property (nonatomic) NSString *oldText;
@end
@ -259,7 +262,9 @@ typedef enum : NSUInteger {
self.scrollContinuity = kScrollContinuityBottom;
_mentionStartIndex = -1;
_currentMentionStartIndex = -1;
_mentions = [NSMutableArray new];
_oldText = @"";
}
#pragma mark - Dependencies
@ -528,7 +533,7 @@ typedef enum : NSUInteger {
userInfo:nil
repeats:YES];
[LKAPI populateUserIDCacheIfNeededFor:thread.uniqueId in:nil];
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:thread.uniqueId in:nil];
}
- (void)dealloc
@ -3019,6 +3024,7 @@ typedef enum : NSUInteger {
{
[self tryToSendAttachments:attachments messageText:messageText];
[self.inputToolbar clearTextMessageAnimated:NO];
[self resetMentions];
// we want to already be at the bottom when the user returns, rather than have to watch
// the new message scroll into view.
@ -3770,35 +3776,70 @@ typedef enum : NSUInteger {
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.text.length > 0) {
// Prepare
NSString *newText = textView.text;
// Typing indicators
if (newText.length > 0) {
[self.typingIndicators didStartTypingOutgoingInputInThread:self.thread];
}
NSUInteger currentEndIndex = (textView.text.length != 0) ? textView.text.length - 1 : 0;
unichar lastCharacter = [textView.text characterAtIndex:currentEndIndex];
NSMutableCharacterSet *allowedCharacters = NSMutableCharacterSet.lowercaseLetterCharacterSet;
[allowedCharacters formUnionWithCharacterSet:NSCharacterSet.uppercaseLetterCharacterSet];
if (lastCharacter == '@') {
NSArray<NSString *> *userIDs = [LKAPI getUserIDsFor:@"" in:self.thread.uniqueId];
self.mentionStartIndex = (NSInteger)currentEndIndex + 1;
[self.inputToolbar showUserSelectionViewFor:userIDs in:self.thread];
} else if (![allowedCharacters characterIsMember:lastCharacter]) {
self.mentionStartIndex = -1;
[self.inputToolbar hideUserSelectionView];
} else {
if (self.mentionStartIndex != -1) {
NSString *query = [textView.text substringFromIndex:(NSUInteger)self.mentionStartIndex];
NSArray<NSString *> *userIDs = [LKAPI getUserIDsFor:query in:self.thread.uniqueId];
[self.inputToolbar showUserSelectionViewFor:userIDs in:self.thread];
// Mentions
BOOL isBackspace = newText.length < self.oldText.length;
if (isBackspace) {
self.currentMentionStartIndex = -1;
[self.inputToolbar hideMentionCandidateSelectionView];
for (LKMention *mention in self.mentions) {
if (![mention isContainedIn:newText]) {
[self.mentions removeObject:mention];
}
}
} else if (newText.length > 0) {
NSUInteger lastCharacterIndex = newText.length - 1;
unichar lastCharacter = [newText characterAtIndex:lastCharacterIndex];
if (lastCharacter == '@') {
NSArray<LKMention *> *mentionCandidates = [LKAPI getMentionCandidatesFor:@"" in:self.thread.uniqueId];
self.currentMentionStartIndex = (NSInteger)lastCharacterIndex;
[self.inputToolbar showMentionCandidateSelectionViewFor:mentionCandidates in:self.thread];
} else if ([NSCharacterSet.whitespaceAndNewlineCharacterSet characterIsMember:lastCharacter]) {
self.currentMentionStartIndex = -1;
[self.inputToolbar hideMentionCandidateSelectionView];
} else {
if (self.currentMentionStartIndex != -1) {
NSString *query = [newText substringFromIndex:(NSUInteger)self.currentMentionStartIndex + 1]; // + 1 to get rid of the @
NSArray<LKMention *> *mentionCandidates = [LKAPI getMentionCandidatesFor:query in:self.thread.uniqueId];
[self.inputToolbar showMentionCandidateSelectionViewFor:mentionCandidates in:self.thread];
}
}
}
self.oldText = newText;
}
- (void)handleUserSelected:(NSString *)user from:(LKUserSelectionView *)userSelectionView
- (void)handleMentionCandidateSelected:(LKMention *)mentionCandidate from:(LKMentionCandidateSelectionView *)mentionCandidateSelectionView
{
NSUInteger mentionStartIndex = (NSUInteger)self.currentMentionStartIndex;
[self.mentions addObject:mentionCandidate];
NSString *oldText = self.inputToolbar.messageText;
NSUInteger mentionStartIndex = (NSUInteger)self.mentionStartIndex;
NSString *newText = [oldText stringByReplacingCharactersInRange:NSMakeRange(mentionStartIndex, oldText.length - mentionStartIndex) withString:user];
NSString *newText = [oldText stringByReplacingCharactersInRange:NSMakeRange(mentionStartIndex, oldText.length - mentionStartIndex) withString:[NSString stringWithFormat:@"@%@", mentionCandidate.displayName]];
[self.inputToolbar setMessageText:newText animated:NO];
self.currentMentionStartIndex = -1;
[self.inputToolbar hideMentionCandidateSelectionView];
self.oldText = newText;
}
- (NSString *)getSendText
{
NSString *result = self.inputToolbar.messageText;
for (LKMention *mention in self.mentions) {
NSRange range = [result rangeOfString:[NSString stringWithFormat:@"@%@", mention.displayName]];
result = [result stringByReplacingCharactersInRange:range withString:[[NSString alloc] initWithFormat:@"@%@", mention.hexEncodedPublicKey]];
}
return result;
}
- (void)resetMentions
{
self.oldText = @"";
self.currentMentionStartIndex = -1;
self.mentions = @[].mutableCopy;
}
- (void)inputTextViewSendMessagePressed
@ -4049,6 +4090,7 @@ typedef enum : NSUInteger {
{
[self tryToSendAttachments:attachments messageText:messageText];
[self.inputToolbar clearTextMessageAnimated:NO];
[self resetMentions];
[self dismissViewControllerAnimated:YES completion:nil];
// We always want to scroll to the bottom of the conversation after the local user
@ -4419,7 +4461,8 @@ typedef enum : NSUInteger {
[BenchManager startEventWithTitle:@"Send Message milestone: toggleDefaultKeyboard completed"
eventId:@"fromSendUntil_toggleDefaultKeyboard"];
[self tryToSendTextMessage:self.inputToolbar.messageText updateKeyboardState:YES];
[self.inputToolbar hideMentionCandidateSelectionView];
[self tryToSendTextMessage:[self getSendText] updateKeyboardState:YES];
}
- (void)tryToSendTextMessage:(NSString *)text updateKeyboardState:(BOOL)updateKeyboardState
@ -4477,6 +4520,7 @@ typedef enum : NSUInteger {
[BenchManager benchWithTitle:@"clearTextMessageAnimated"
block:^{
[self.inputToolbar clearTextMessageAnimated:YES];
[self resetMentions];
}];
[BenchManager completeEventWithEventId:@"fromSendUntil_clearTextMessageAnimated"];

View File

@ -395,6 +395,8 @@ NS_ASSUME_NONNULL_BEGIN
}
NSString *displayableText = thread.lastMessageText;
if (displayableText) {
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor: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:@{

View File

@ -393,12 +393,13 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
__weak ProfileViewController *weakSelf = self;
NSString *normalizedProfileName = [self normalizedProfileName];
if (normalizedProfileName.length == 0) {
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a display name", @"")];
}
if ([OWSProfileManager.sharedManager isProfileNameTooLong:normalizedProfileName]) {
[OWSAlerts
showErrorAlertWithMessage:NSLocalizedString(@"PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG",
@"Error message shown when user tries to update profile with a profile name "
@"that is too long.")];
return;
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a shorter display name", @"")];
}
[LKAnalytics.shared track:@"Display Name Updated"];

View File

@ -2547,7 +2547,7 @@
"Loki Messenger can let you know when you get a message (and who it is from)" = "Loki Messenger can let you know when you get a message (and who it is from)";
"Create Your Loki Messenger Account" = "Create Your Loki Messenger Account";
"Enter a name to be shown to your contacts" = "Enter a name to be shown to your contacts";
"Display Name (Optional)" = "Display Name (Optional)";
"Display Name" = "Display Name";
"Type an optional password for added security" = "Type an optional password for added security";
"Password (Optional)" = "Password (Optional)";
"Next" = "Next";
@ -2593,7 +2593,6 @@
"Your Seed" = "Your Seed";
"Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed." = "Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed.";
"Prevent Loki Messenger previews from appearing in the app switcher." = "Prevent Loki Messenger previews from appearing in the app switcher.";
"Display Name" = "Display Name";
"Loki Messenger" = "Loki Messenger";
"Privacy Policy" = "Privacy Policy";
"New Conversation" = "New Conversation";
@ -2643,3 +2642,5 @@
"Anonymous" = "Anonymous";
"Invalid server URL provided" = "Invalid server URL provided";
"Please make sure you have provided the full url" = "Please make sure you have provided the full url. E.g https://public-chat-server.url/";
"Please pick a shorter display name" = "Please pick a shorter display name";
"Please pick a display name" = "Please pick a display name";

View File

@ -5,7 +5,7 @@ public final class LokiGroupChatAPI : LokiDotNetAPI {
private static var moderators: [String:[UInt64:Set<String>]] = [:] // Server URL to (channel ID to set of moderator IDs)
// MARK: Settings
private static let fallbackBatchCount = 20
private static let fallbackBatchCount = 256
private static let maxRetryCount: UInt = 8
// MARK: Public Chat

View File

@ -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 userHexEncodedPublicKeyCache: [String:Set<String>] = [:] // Thread ID to set of user hex encoded public keys
// MARK: Convenience
internal static let storage = OWSPrimaryStorage.shared()
@ -296,42 +296,43 @@ public final class LokiAPI : NSObject {
}
// MARK: User ID Caching
@objc public static func cache(_ userHexEncodedPublicKey: String, for threadID: String) {
if let cache = userIDCache[threadID] {
var mutableCache = cache
mutableCache.insert(userHexEncodedPublicKey)
userIDCache[threadID] = mutableCache
@objc public static func cache(_ hexEncodedPublicKey: String, for threadID: String) {
if let cache = userHexEncodedPublicKeyCache[threadID] {
userHexEncodedPublicKeyCache[threadID] = cache.union([ hexEncodedPublicKey ])
} else {
userIDCache[threadID] = [ userHexEncodedPublicKey ]
userHexEncodedPublicKeyCache[threadID] = [ hexEncodedPublicKey ]
}
}
@objc public static func getUserIDs(for query: String, in threadID: String) -> [String] {
@objc public static func getMentionCandidates(for query: String, in threadID: String) -> [Mention] {
// Prepare
guard let cache = userIDCache[threadID] else { return [] }
var candidates: [(id: String, displayName: String)] = []
guard let cache = userHexEncodedPublicKeyCache[threadID] else { return [] }
var candidates: [Mention] = []
// Gather candidates
storage.dbReadConnection.read { transaction in
let collection = "\(LokiGroupChatAPI.publicChatServer).\(LokiGroupChatAPI.publicChatServerID)"
candidates = cache.flatMap { id in
guard let displayName = transaction.object(forKey: id, inCollection: collection) as! String? else { return nil }
return (id: id, displayName: displayName)
candidates = cache.flatMap { hexEncodedPublicKey in
guard let displayName = transaction.object(forKey: hexEncodedPublicKey, inCollection: collection) as! String? else { return nil }
guard !displayName.hasPrefix("Anonymous") else { return nil }
return Mention(hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName)
}
}
// Sort alphabetically first
candidates.sort { $0.displayName < $1.displayName }
if query.count >= 2 {
// Filter out any non-matching candidates
candidates = candidates.filter { $0.displayName.contains(query) }
candidates = candidates.filter { $0.displayName.lowercased().contains(query.lowercased()) }
// Sort based on where in the candidate the query occurs
candidates.sort { $0.displayName.range(of: query)!.lowerBound < $1.displayName.range(of: query)!.lowerBound }
candidates.sort {
$0.displayName.lowercased().range(of: query.lowercased())!.lowerBound < $1.displayName.lowercased().range(of: query.lowercased())!.lowerBound
}
}
// Return
return candidates.map { $0.id } // Inefficient to do this and then look up the display name again later, but easy to interface with Obj-C
return candidates
}
@objc public static func populateUserIDCacheIfNeeded(for threadID: String, in transaction: YapDatabaseReadWriteTransaction? = nil) {
guard userIDCache[threadID] == nil else { return }
@objc public static func populateUserHexEncodedPublicKeyCacheIfNeeded(for threadID: String, in transaction: YapDatabaseReadWriteTransaction? = nil) {
guard userHexEncodedPublicKeyCache[threadID] == nil else { return }
var result: Set<String> = []
func populate(in transaction: YapDatabaseReadWriteTransaction) {
guard let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return }
@ -349,7 +350,7 @@ public final class LokiAPI : NSObject {
}
}
result.insert(userHexEncodedPublicKey)
userIDCache[threadID] = result
userHexEncodedPublicKeyCache[threadID] = result
}
}

View File

@ -0,0 +1,15 @@
@objc(LKMention)
public final class Mention : NSObject {
@objc public let hexEncodedPublicKey: String
@objc public let displayName: String
@objc public init(hexEncodedPublicKey: String, displayName: String) {
self.hexEncodedPublicKey = hexEncodedPublicKey
self.displayName = displayName
}
@objc public func isContained(in string: String) -> Bool {
return string.contains(displayName)
}
}

View File

@ -1414,7 +1414,7 @@ NS_ASSUME_NONNULL_BEGIN
}
// Loki: Cache the user hex encoded public key (for mentions)
[LKAPI populateUserIDCacheIfNeededFor:oldGroupThread.uniqueId in:transaction];
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:oldGroupThread.uniqueId in:transaction];
[LKAPI cache:incomingMessage.authorId for:oldGroupThread.uniqueId];
[self finalizeIncomingMessage:incomingMessage