Merge branch 'mkirk/fts-contacts'

pull/1/head
Michael Kirk 7 years ago
commit ecf9a5689c

@ -34,36 +34,49 @@ class ConversationSearchViewController: UITableViewController {
tableView.register(ConversationSearchResultCell.self, forCellReuseIdentifier: ConversationSearchResultCell.reuseIdentifier) tableView.register(ConversationSearchResultCell.self, forCellReuseIdentifier: ConversationSearchResultCell.reuseIdentifier)
tableView.register(MessageSearchResultCell.self, forCellReuseIdentifier: MessageSearchResultCell.reuseIdentifier) tableView.register(MessageSearchResultCell.self, forCellReuseIdentifier: MessageSearchResultCell.reuseIdentifier)
tableView.register(ContactSearchResultCell.self, forCellReuseIdentifier: ContactSearchResultCell.reuseIdentifier)
} }
// MARK: UITableViewDelegate // MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false) tableView.deselectRow(at: indexPath, animated: false)
guard let searchSection = SearchSection(rawValue: indexPath.section) else { guard let searchSection = SearchSection(rawValue: indexPath.section) else {
owsFail("\(logTag) unknown section selected.") owsFail("\(logTag) unknown section selected.")
return return
} }
var sectionResults: [SearchResult]
switch searchSection { switch searchSection {
case .conversations: case .conversations:
sectionResults = searchResultSet.conversations let sectionResults = searchResultSet.conversations
guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFail("\(logTag) unknown row selected.")
return
}
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord, action: .compose)
case .contacts: case .contacts:
sectionResults = searchResultSet.contacts let sectionResults = searchResultSet.contacts
guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFail("\(logTag) unknown row selected.")
return
}
SignalApp.shared().presentConversation(forRecipientId: searchResult.recipientId, action: .compose)
case .messages: case .messages:
sectionResults = searchResultSet.messages let sectionResults = searchResultSet.messages
} guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFail("\(logTag) unknown row selected.")
guard indexPath.row < sectionResults.count else { return
owsFail("\(logTag) unknown row selected.") }
return
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord, action: .compose)
} }
let searchResult = sectionResults[indexPath.row]
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord, action: .compose)
} }
// MARK: UITableViewDataSource // MARK: UITableViewDataSource
@ -102,8 +115,16 @@ class ConversationSearchViewController: UITableViewController {
cell.configure(searchResult: searchResult) cell.configure(searchResult: searchResult)
return cell return cell
case .contacts: case .contacts:
// TODO guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactSearchResultCell.reuseIdentifier) as? ContactSearchResultCell else {
return UITableViewCell() return UITableViewCell()
}
guard let searchResult = self.searchResultSet.contacts[safe: indexPath.row] else {
return UITableViewCell()
}
cell.configure(searchResult: searchResult)
return cell
case .messages: case .messages:
guard let cell = tableView.dequeueReusableCell(withIdentifier: MessageSearchResultCell.reuseIdentifier) as? MessageSearchResultCell else { guard let cell = tableView.dequeueReusableCell(withIdentifier: MessageSearchResultCell.reuseIdentifier) as? MessageSearchResultCell else {
return UITableViewCell() return UITableViewCell()
@ -209,7 +230,7 @@ class ConversationSearchResultCell: UITableViewCell {
return Environment.current().contactsManager 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.avatarView.image = OWSAvatarBuilder.buildImage(thread: searchResult.thread.threadRecord, diameter: avatarWidth, contactsManager: self.contactsManager)
self.nameLabel.text = searchResult.thread.name self.nameLabel.text = searchResult.thread.name
self.snippetLabel.text = searchResult.snippet self.snippetLabel.text = searchResult.snippet
@ -242,7 +263,7 @@ class MessageSearchResultCell: UITableViewCell {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func configure(searchResult: SearchResult) { func configure(searchResult: ConversationSearchResult) {
self.nameLabel.text = searchResult.thread.name self.nameLabel.text = searchResult.thread.name
guard let snippet = searchResult.snippet else { guard let snippet = searchResult.snippet else {
@ -272,3 +293,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
}
}

@ -5,7 +5,7 @@
import Foundation import Foundation
import SignalServiceKit import SignalServiceKit
public class SearchResult { public class ConversationSearchResult {
public let thread: ThreadViewModel public let thread: ThreadViewModel
public let snippet: String? 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 class SearchResultSet {
public let conversations: [SearchResult] public let conversations: [ConversationSearchResult]
public let contacts: [SearchResult] public let contacts: [ContactSearchResult]
public let messages: [SearchResult] public let messages: [ConversationSearchResult]
public init(conversations: [SearchResult], contacts: [SearchResult], messages: [SearchResult]) { public init(conversations: [ConversationSearchResult], contacts: [ContactSearchResult], messages: [ConversationSearchResult]) {
self.conversations = conversations self.conversations = conversations
self.contacts = contacts self.contacts = contacts
self.messages = messages self.messages = messages
@ -44,32 +55,42 @@ public class ConversationSearcher: NSObject {
} }
public func results(searchText: String, transaction: YapDatabaseReadTransaction) -> SearchResultSet { public func results(searchText: String, transaction: YapDatabaseReadTransaction) -> SearchResultSet {
var conversations: [SearchResult] = [] var conversations: [ConversationSearchResult] = []
var contacts: [SearchResult] = [] var contacts: [ContactSearchResult] = []
var messages: [SearchResult] = [] var messages: [ConversationSearchResult] = []
var existingConversationRecipientIds: Set<String> = Set()
self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in
if let thread = match as? TSThread { if let thread = match as? TSThread {
let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) let threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
let snippet: String? = thread.lastMessageText(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) conversations.append(searchResult)
} else if let message = match as? TSMessage { } else if let message = match as? TSMessage {
let thread = message.thread(with: transaction) let thread = message.thread(with: transaction)
let threadViewModel = ThreadViewModel(thread: thread, transaction: 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) messages.append(searchResult)
} else if let signalAccount = match as? SignalAccount { } 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 { } else {
Logger.debug("\(self.logTag) in \(#function) unhandled item: \(match)") 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 1:1 conversation.
let otherContacts: [ContactSearchResult] = contacts.filter { !existingConversationRecipientIds.contains($0.recipientId) }
return SearchResultSet(conversations: conversations, contacts: otherContacts, messages: messages)
} }
@objc(filterThreads:withSearchText:) @objc(filterThreads:withSearchText:)

@ -91,13 +91,13 @@ public class FullTextSearchFinder: NSObject {
private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String) in private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String) in
let displayName = contactsManager.displayName(forPhoneIdentifier: recipientId) let displayName = contactsManager.displayName(forPhoneIdentifier: recipientId)
let searchableContent = "\(recipientId) \(displayName)" let searchableContent = "\(recipientId) \(displayName)"
return normalize(text: searchableContent) return normalize(text: searchableContent)
} }
private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage) in private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage) in
let searchableContent = message.body ?? "" let searchableContent = message.body ?? ""
return normalize(text: searchableContent) return normalize(text: searchableContent)
} }
@ -106,9 +106,17 @@ public class FullTextSearchFinder: NSObject {
if let groupThread = object as? TSGroupThread { if let groupThread = object as? TSGroupThread {
return self.groupThreadIndexer.index(groupThread) return self.groupThreadIndexer.index(groupThread)
} else if let contactThread = object as? TSContactThread { } else if let contactThread = object as? TSContactThread {
guard contactThread.hasEverHadMessage else {
// If we've never sent/received a message in a TSContactThread,
// then we want it to appear in the "Other Contacts" section rather
// than in the "Conversations" section.
return nil
}
return self.contactThreadIndexer.index(contactThread) return self.contactThreadIndexer.index(contactThread)
} else if let message = object as? TSMessage { } else if let message = object as? TSMessage {
return self.messageIndexer.index(message) return self.messageIndexer.index(message)
} else if let signalAccount = object as? SignalAccount {
return self.recipientIndexer.index(signalAccount.recipientId)
} else { } else {
return nil return nil
} }

Loading…
Cancel
Save