From f415827da68987ac05ff9e31d164e06c283b3843 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 11 Jun 2018 12:20:49 -0400 Subject: [PATCH] Contact search results // FREEBIE --- .../ConversationSearchViewController.swift | 63 +++++++++++++++++-- .../utils/ConversationSearcher.swift | 45 +++++++++---- .../src/Storage/FullTextSearchFinder.swift | 6 +- 3 files changed, 96 insertions(+), 18 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift index 5d6d27f8e..90599fcb7 100644 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift @@ -34,6 +34,7 @@ class ConversationSearchViewController: UITableViewController { tableView.register(ConversationSearchResultCell.self, forCellReuseIdentifier: ConversationSearchResultCell.reuseIdentifier) tableView.register(MessageSearchResultCell.self, forCellReuseIdentifier: MessageSearchResultCell.reuseIdentifier) + tableView.register(ContactSearchResultCell.self, forCellReuseIdentifier: ContactSearchResultCell.reuseIdentifier) } // MARK: UITableViewDelegate @@ -102,8 +103,16 @@ class ConversationSearchViewController: UITableViewController { cell.configure(searchResult: searchResult) return cell case .contacts: - // TODO - return UITableViewCell() + guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactSearchResultCell.reuseIdentifier) as? ContactSearchResultCell else { + return UITableViewCell() + } + + guard let searchResult = self.searchResultSet.contacts[safe: indexPath.row] else { + return UITableViewCell() + } + + cell.configure(searchResult: searchResult) + return cell case .messages: guard let cell = tableView.dequeueReusableCell(withIdentifier: MessageSearchResultCell.reuseIdentifier) as? MessageSearchResultCell else { return UITableViewCell() @@ -209,7 +218,7 @@ class ConversationSearchResultCell: UITableViewCell { return Environment.current().contactsManager } - func configure(searchResult: SearchResult) { + func configure(searchResult: ConversationSearchResult) { self.avatarView.image = OWSAvatarBuilder.buildImage(thread: searchResult.thread.threadRecord, diameter: avatarWidth, contactsManager: self.contactsManager) self.nameLabel.text = searchResult.thread.name self.snippetLabel.text = searchResult.snippet @@ -242,7 +251,7 @@ class MessageSearchResultCell: UITableViewCell { fatalError("init(coder:) has not been implemented") } - func configure(searchResult: SearchResult) { + func configure(searchResult: ConversationSearchResult) { self.nameLabel.text = searchResult.thread.name guard let snippet = searchResult.snippet else { @@ -272,3 +281,49 @@ class MessageSearchResultCell: UITableViewCell { } } } + +class ContactSearchResultCell: UITableViewCell { + static let reuseIdentifier = "ContactSearchResultCell" + + let nameLabel: UILabel + let snippetLabel: UILabel + let avatarView: AvatarImageView + let avatarWidth: UInt = 40 + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + self.nameLabel = UILabel() + self.snippetLabel = UILabel() + self.avatarView = AvatarImageView() + avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarWidth), height: CGFloat(avatarWidth))) + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + nameLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() + snippetLabel.font = UIFont.ows_dynamicTypeFootnote + + let textRows = UIStackView(arrangedSubviews: [nameLabel, snippetLabel]) + textRows.axis = .vertical + + let columns = UIStackView(arrangedSubviews: [avatarView, textRows]) + columns.axis = .horizontal + columns.spacing = 8 + + contentView.addSubview(columns) + columns.autoPinEdgesToSuperviewMargins() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var contactsManager: OWSContactsManager { + return Environment.current().contactsManager + } + + func configure(searchResult: ContactSearchResult) { + let avatarBuilder = OWSContactAvatarBuilder.init(signalId: searchResult.recipientId, diameter: avatarWidth, contactsManager: contactsManager) + self.avatarView.image = avatarBuilder.build() + self.nameLabel.text = self.contactsManager.displayName(forPhoneIdentifier: searchResult.recipientId) + self.snippetLabel.text = searchResult.recipientId + } +} diff --git a/SignalMessaging/utils/ConversationSearcher.swift b/SignalMessaging/utils/ConversationSearcher.swift index 644bb2195..f2ff4f53d 100644 --- a/SignalMessaging/utils/ConversationSearcher.swift +++ b/SignalMessaging/utils/ConversationSearcher.swift @@ -5,7 +5,7 @@ import Foundation import SignalServiceKit -public class SearchResult { +public class ConversationSearchResult { public let thread: ThreadViewModel public let snippet: String? @@ -15,12 +15,23 @@ public class SearchResult { } } +public class ContactSearchResult { + public let signalAccount: SignalAccount + public var recipientId: String { + return signalAccount.recipientId + } + + init(signalAccount: SignalAccount) { + self.signalAccount = signalAccount + } +} + public class SearchResultSet { - public let conversations: [SearchResult] - public let contacts: [SearchResult] - public let messages: [SearchResult] + public let conversations: [ConversationSearchResult] + public let contacts: [ContactSearchResult] + public let messages: [ConversationSearchResult] - public init(conversations: [SearchResult], contacts: [SearchResult], messages: [SearchResult]) { + public init(conversations: [ConversationSearchResult], contacts: [ContactSearchResult], messages: [ConversationSearchResult]) { self.conversations = conversations self.contacts = contacts self.messages = messages @@ -44,32 +55,42 @@ public class ConversationSearcher: NSObject { } public func results(searchText: String, transaction: YapDatabaseReadTransaction) -> SearchResultSet { - var conversations: [SearchResult] = [] - var contacts: [SearchResult] = [] - var messages: [SearchResult] = [] + var conversations: [ConversationSearchResult] = [] + var contacts: [ContactSearchResult] = [] + var messages: [ConversationSearchResult] = [] + + var existingConversationRecipientIds: Set = Set() self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in if let thread = match as? TSThread { let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let snippet: String? = thread.lastMessageText(transaction: transaction) - let searchResult = SearchResult(thread: threadViewModel, snippet: snippet) + let searchResult = ConversationSearchResult(thread: threadViewModel, snippet: snippet) + if let contactThread = thread as? TSContactThread { + let recipientId = contactThread.contactIdentifier() + existingConversationRecipientIds.insert(recipientId) + } conversations.append(searchResult) } else if let message = match as? TSMessage { let thread = message.thread(with: transaction) let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) - let searchResult = SearchResult(thread: threadViewModel, snippet: snippet) + let searchResult = ConversationSearchResult(thread: threadViewModel, snippet: snippet) messages.append(searchResult) } else if let signalAccount = match as? SignalAccount { - // TODO show "other contact" results when there is no existing thread + let searchResult = ContactSearchResult(signalAccount: signalAccount) + contacts.append(searchResult) } else { Logger.debug("\(self.logTag) in \(#function) unhandled item: \(match)") } } - return SearchResultSet(conversations: conversations, contacts: contacts, messages: messages) + // Only show contacts which were not included in an existing conversation. + let otherContacts: [ContactSearchResult] = contacts.filter { !existingConversationRecipientIds.contains($0.recipientId) } + + return SearchResultSet(conversations: conversations, contacts: otherContacts, messages: messages) } @objc(filterThreads:withSearchText:) diff --git a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift index 5019d5fdf..c3d60a41d 100644 --- a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift +++ b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift @@ -91,13 +91,13 @@ public class FullTextSearchFinder: NSObject { private static let recipientIndexer: SearchIndexer = SearchIndexer { (recipientId: String) in let displayName = contactsManager.displayName(forPhoneIdentifier: recipientId) - let searchableContent = "\(recipientId) \(displayName)" + let searchableContent = "\(recipientId) \(displayName)" return normalize(text: searchableContent) } private static let messageIndexer: SearchIndexer = SearchIndexer { (message: TSMessage) in - let searchableContent = message.body ?? "" + let searchableContent = message.body ?? "" return normalize(text: searchableContent) } @@ -109,6 +109,8 @@ public class FullTextSearchFinder: NSObject { return self.contactThreadIndexer.index(contactThread) } else if let message = object as? TSMessage { return self.messageIndexer.index(message) + } else if let signalAccount = object as? SignalAccount { + return self.recipientIndexer.index(signalAccount.recipientId) } else { return nil }