mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Change mentions approach
This commit is contained in:
parent
3cd1febbb5
commit
9b47c646fe
12 changed files with 101 additions and 118 deletions
|
@ -567,7 +567,6 @@
|
|||
B82584A02315024B001B41CB /* RSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* RSSFeedPoller.swift */; };
|
||||
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 */; };
|
||||
|
@ -580,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 */; };
|
||||
|
@ -1380,7 +1379,6 @@
|
|||
B825849F2315024B001B41CB /* RSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSFeedPoller.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
|
@ -1391,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,12 +2654,11 @@
|
|||
B8162F0222891AD600D46544 /* FriendRequestView.swift */,
|
||||
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
|
||||
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
|
||||
B84664F2234FE4530083A1CD /* Mention.swift */,
|
||||
B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */,
|
||||
B8258491230FA5DA001B41CB /* ScanQRCodeVC.h */,
|
||||
B8258492230FA5E9001B41CB /* ScanQRCodeVC.m */,
|
||||
B8B26C8E234D629C004ED98C /* UserSelectionView.swift */,
|
||||
B8B26C90234D8CBD004ED98C /* UserSelectionViewDelegate.swift */,
|
||||
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
||||
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
|
||||
);
|
||||
path = Loki;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3852,7 +3849,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 */,
|
||||
|
@ -3875,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 */,
|
||||
|
@ -3886,7 +3883,6 @@
|
|||
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 */,
|
||||
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
// 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
|
||||
@objc lazy var tableView: UITableView = { // TODO: Make this private
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
@objc(LKMentionCandidateSelectionViewDelegate)
|
||||
protocol MentionCandidateSelectionViewDelegate {
|
||||
|
||||
func handleMentionCandidateSelected(_ mentionCandidate: Mention, from mentionCandidateSelectionView: MentionCandidateSelectionView)
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
@objc(LKUserSelectionViewDelegate)
|
||||
protocol UserSelectionViewDelegate {
|
||||
|
||||
func handleUserSelected(_ user: String, from userSelectionView: UserSelectionView)
|
||||
}
|
|
@ -11,7 +11,7 @@ public final class MentionUtilities : NSObject {
|
|||
@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
|
||||
let knownUserIDs = 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() {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,28 +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 = 6 + 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.userSelectionView.tableView setContentOffset:CGPointMake(0, -6)]; // TODO: Workaround for content offset bug
|
||||
[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
|
||||
|
|
|
@ -533,7 +533,7 @@ typedef enum : NSUInteger {
|
|||
userInfo:nil
|
||||
repeats:YES];
|
||||
|
||||
[LKAPI populateUserIDCacheIfNeededFor:thread.uniqueId in:nil];
|
||||
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:thread.uniqueId in:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
@ -3023,7 +3023,7 @@ typedef enum : NSUInteger {
|
|||
{
|
||||
[self tryToSendAttachments:attachments messageText:messageText];
|
||||
[self.inputToolbar clearTextMessageAnimated:NO];
|
||||
[self clearMentions];
|
||||
[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.
|
||||
|
@ -3785,68 +3785,56 @@ typedef enum : NSUInteger {
|
|||
BOOL isBackspace = newText.length < self.oldText.length;
|
||||
if (isBackspace) {
|
||||
self.currentMentionStartIndex = -1;
|
||||
[self.inputToolbar hideMentionCandidateSelectionView];
|
||||
for (LKMention *mention in self.mentions) {
|
||||
BOOL isValid;
|
||||
if (mention.locationInString > (NSUInteger)MAX((NSInteger)newText.length - 1, 0)) {
|
||||
isValid = NO;
|
||||
} else {
|
||||
isValid = [[newText substringFromIndex:mention.locationInString] hasPrefix:[NSString stringWithFormat:@"@%@", mention.displayName]];
|
||||
}
|
||||
if (!isValid) {
|
||||
if (![mention isContainedIn:newText]) {
|
||||
[self.mentions removeObject:mention];
|
||||
}
|
||||
}
|
||||
} else if (newText.length > 0) {
|
||||
NSUInteger currentEndIndex = newText.length - 1;
|
||||
unichar lastCharacter = [newText characterAtIndex:currentEndIndex];
|
||||
NSUInteger lastCharacterIndex = newText.length - 1;
|
||||
unichar lastCharacter = [newText characterAtIndex:lastCharacterIndex];
|
||||
if (lastCharacter == '@') {
|
||||
NSArray<NSString *> *userIDs = [LKAPI getUserIDsFor:@"" in:self.thread.uniqueId];
|
||||
self.currentMentionStartIndex = (NSInteger)currentEndIndex;
|
||||
[self.inputToolbar showUserSelectionViewFor:userIDs in:self.thread];
|
||||
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 hideUserSelectionView];
|
||||
[self.inputToolbar hideMentionCandidateSelectionView];
|
||||
} else {
|
||||
if (self.currentMentionStartIndex != -1) {
|
||||
NSString *query = [newText substringFromIndex:(NSUInteger)self.currentMentionStartIndex + 1]; // + 1 to get rid of the @
|
||||
NSArray<NSString *> *userIDs = [LKAPI getUserIDsFor:query in:self.thread.uniqueId];
|
||||
[self.inputToolbar showUserSelectionViewFor:userIDs in:self.thread];
|
||||
NSArray<LKMention *> *mentionCandidates = [LKAPI getMentionCandidatesFor:query in:self.thread.uniqueId];
|
||||
[self.inputToolbar showMentionCandidateSelectionViewFor:mentionCandidates in:self.thread];
|
||||
}
|
||||
}
|
||||
}
|
||||
self.oldText = newText;
|
||||
}
|
||||
|
||||
- (void)handleUserSelected:(NSString *)hexEncodedPublicKey from:(LKUserSelectionView *)userSelectionView
|
||||
- (void)handleMentionCandidateSelected:(LKMention *)mentionCandidate from:(LKMentionCandidateSelectionView *)mentionCandidateSelectionView
|
||||
{
|
||||
NSUInteger mentionStartIndex = (NSUInteger)self.currentMentionStartIndex;
|
||||
__block NSString *displayName;
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSString *collection = [NSString stringWithFormat:@"%@.%llu", LKGroupChatAPI.publicChatServer, LKGroupChatAPI.publicChatServerID];
|
||||
displayName = [transaction objectForKey:hexEncodedPublicKey inCollection:collection];
|
||||
}];
|
||||
LKMention *mention = [[LKMention alloc] initWithLocationInString:mentionStartIndex hexEncodedPublicKey:hexEncodedPublicKey displayName:displayName];
|
||||
[self.mentions addObject:mention];
|
||||
[self.mentions addObject:mentionCandidate];
|
||||
NSString *oldText = self.inputToolbar.messageText;
|
||||
NSString *newText = [oldText stringByReplacingCharactersInRange:NSMakeRange(mentionStartIndex, oldText.length - mentionStartIndex) withString:[NSString stringWithFormat:@"@%@", displayName]];
|
||||
NSString *newText = [oldText stringByReplacingCharactersInRange:NSMakeRange(mentionStartIndex, oldText.length - mentionStartIndex) withString:[NSString stringWithFormat:@"@%@", mentionCandidate.displayName]];
|
||||
[self.inputToolbar setMessageText:newText animated:NO];
|
||||
[self.inputToolbar hideUserSelectionView];
|
||||
self.currentMentionStartIndex = -1;
|
||||
[self.inputToolbar hideMentionCandidateSelectionView];
|
||||
self.oldText = newText;
|
||||
}
|
||||
|
||||
- (NSString *)getSendText
|
||||
{
|
||||
NSString *result = self.inputToolbar.messageText;
|
||||
NSUInteger shift = 0;
|
||||
for (LKMention *mention in self.mentions) {
|
||||
NSRange range = NSMakeRange(mention.locationInString + shift, mention.displayName.length + 1); // + 1 to include the @
|
||||
shift = shift + mention.hexEncodedPublicKey.length - mention.displayName.length;
|
||||
NSRange range = [result rangeOfString:[NSString stringWithFormat:@"@%@", mention.displayName]];
|
||||
result = [result stringByReplacingCharactersInRange:range withString:[[NSString alloc] initWithFormat:@"@%@", mention.hexEncodedPublicKey]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)clearMentions
|
||||
- (void)resetMentions
|
||||
{
|
||||
self.oldText = @"";
|
||||
self.currentMentionStartIndex = -1;
|
||||
|
@ -4101,7 +4089,7 @@ typedef enum : NSUInteger {
|
|||
{
|
||||
[self tryToSendAttachments:attachments messageText:messageText];
|
||||
[self.inputToolbar clearTextMessageAnimated:NO];
|
||||
[self clearMentions];
|
||||
[self resetMentions];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
// We always want to scroll to the bottom of the conversation after the local user
|
||||
|
@ -4472,6 +4460,7 @@ typedef enum : NSUInteger {
|
|||
[BenchManager startEventWithTitle:@"Send Message milestone: toggleDefaultKeyboard completed"
|
||||
eventId:@"fromSendUntil_toggleDefaultKeyboard"];
|
||||
|
||||
[self.inputToolbar hideMentionCandidateSelectionView];
|
||||
[self tryToSendTextMessage:[self getSendText] updateKeyboardState:YES];
|
||||
}
|
||||
|
||||
|
@ -4530,7 +4519,7 @@ typedef enum : NSUInteger {
|
|||
[BenchManager benchWithTitle:@"clearTextMessageAnimated"
|
||||
block:^{
|
||||
[self.inputToolbar clearTextMessageAnimated:YES];
|
||||
[self clearMentions];
|
||||
[self resetMentions];
|
||||
}];
|
||||
[BenchManager completeEventWithEventId:@"fromSendUntil_clearTextMessageAnimated"];
|
||||
|
||||
|
|
|
@ -395,7 +395,7 @@ 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
|
||||
[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
|
||||
|
|
|
@ -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 public 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,26 +296,25 @@ 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
|
||||
|
@ -329,11 +328,11 @@ public final class LokiAPI : NSObject {
|
|||
}
|
||||
}
|
||||
// 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 }
|
||||
|
@ -351,7 +350,7 @@ public final class LokiAPI : NSObject {
|
|||
}
|
||||
}
|
||||
result.insert(userHexEncodedPublicKey)
|
||||
userIDCache[threadID] = result
|
||||
userHexEncodedPublicKeyCache[threadID] = result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
|
||||
@objc(LKMention)
|
||||
public final class Mention : NSObject {
|
||||
@objc public let locationInString: UInt
|
||||
@objc public let hexEncodedPublicKey: String
|
||||
@objc public let displayName: String
|
||||
|
||||
@objc public init(locationInString: UInt, hexEncodedPublicKey: String, displayName: String) {
|
||||
self.locationInString = locationInString
|
||||
@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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue