mirror of https://github.com/oxen-io/session-ios
Merge branch 'mkirk/conversation-search'
commit
504416f79e
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-down-24@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-down-24@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-down-24@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 234 B |
Binary file not shown.
After Width: | Height: | Size: 359 B |
Binary file not shown.
After Width: | Height: | Size: 521 B |
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-up-24@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-up-24@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "chevron-up-24@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 235 B |
Binary file not shown.
After Width: | Height: | Size: 351 B |
Binary file not shown.
After Width: | Height: | Size: 516 B |
@ -0,0 +1,289 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public protocol ConversationSearchControllerDelegate: UISearchControllerDelegate {
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func conversationSearchController(_ conversationSearchController: ConversationSearchController,
|
||||||
|
didUpdateSearchResults resultSet: ConversationScreenSearchResultSet?)
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func conversationSearchController(_ conversationSearchController: ConversationSearchController,
|
||||||
|
didSelectMessageId: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class ConversationSearchController: NSObject {
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public static let kMinimumSearchTextLength: UInt = 2
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public let uiSearchController = UISearchController(searchResultsController: nil)
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public weak var delegate: ConversationSearchControllerDelegate?
|
||||||
|
|
||||||
|
let thread: TSThread
|
||||||
|
|
||||||
|
let resultsBar: SearchResultsBar = SearchResultsBar(frame: .zero)
|
||||||
|
|
||||||
|
// MARK: Initializer
|
||||||
|
|
||||||
|
@objc
|
||||||
|
required public init(thread: TSThread) {
|
||||||
|
self.thread = thread
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
resultsBar.resultsBarDelegate = self
|
||||||
|
uiSearchController.delegate = self
|
||||||
|
uiSearchController.searchResultsUpdater = self
|
||||||
|
|
||||||
|
uiSearchController.hidesNavigationBarDuringPresentation = false
|
||||||
|
uiSearchController.dimsBackgroundDuringPresentation = false
|
||||||
|
uiSearchController.searchBar.inputAccessoryView = resultsBar
|
||||||
|
|
||||||
|
applyTheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTheme() {
|
||||||
|
OWSSearchBar.applyTheme(to: uiSearchController.searchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Dependencies
|
||||||
|
|
||||||
|
var dbReadConnection: YapDatabaseConnection {
|
||||||
|
return OWSPrimaryStorage.shared().dbReadConnection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConversationSearchController: UISearchControllerDelegate {
|
||||||
|
public func didPresentSearchController(_ searchController: UISearchController) {
|
||||||
|
Logger.verbose("")
|
||||||
|
delegate?.didPresentSearchController?(searchController)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didDismissSearchController(_ searchController: UISearchController) {
|
||||||
|
Logger.verbose("")
|
||||||
|
delegate?.didDismissSearchController?(searchController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConversationSearchController: UISearchResultsUpdating {
|
||||||
|
var dbSearcher: FullTextSearcher {
|
||||||
|
return FullTextSearcher.shared
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateSearchResults(for searchController: UISearchController) {
|
||||||
|
Logger.verbose("searchBar.text: \( searchController.searchBar.text ?? "<blank>")")
|
||||||
|
|
||||||
|
guard let searchText = searchController.searchBar.text?.stripped else {
|
||||||
|
self.resultsBar.updateResults(resultSet: nil)
|
||||||
|
self.delegate?.conversationSearchController(self, didUpdateSearchResults: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
BenchManager.startEvent(title: "Conversation Search", eventId: searchText)
|
||||||
|
|
||||||
|
guard searchText.count >= ConversationSearchController.kMinimumSearchTextLength else {
|
||||||
|
self.resultsBar.updateResults(resultSet: nil)
|
||||||
|
self.delegate?.conversationSearchController(self, didUpdateSearchResults: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultSet: ConversationScreenSearchResultSet?
|
||||||
|
self.dbReadConnection.asyncRead({ [weak self] transaction in
|
||||||
|
guard let self = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultSet = self.dbSearcher.searchWithinConversation(thread: self.thread, searchText: searchText, transaction: transaction)
|
||||||
|
}, completionBlock: { [weak self] in
|
||||||
|
guard let self = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.resultsBar.updateResults(resultSet: resultSet)
|
||||||
|
self.delegate?.conversationSearchController(self, didUpdateSearchResults: resultSet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConversationSearchController: SearchResultsBarDelegate {
|
||||||
|
func searchResultsBar(_ searchResultsBar: SearchResultsBar,
|
||||||
|
setCurrentIndex currentIndex: Int,
|
||||||
|
resultSet: ConversationScreenSearchResultSet) {
|
||||||
|
guard let searchResult = resultSet.messages[safe: currentIndex] else {
|
||||||
|
owsFailDebug("messageId was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BenchEventStart(title: "Conversation Search Nav", eventId: "Conversation Search Nav: \(searchResult.messageId)")
|
||||||
|
self.delegate?.conversationSearchController(self, didSelectMessageId: searchResult.messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol SearchResultsBarDelegate: AnyObject {
|
||||||
|
func searchResultsBar(_ searchResultsBar: SearchResultsBar,
|
||||||
|
setCurrentIndex currentIndex: Int,
|
||||||
|
resultSet: ConversationScreenSearchResultSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchResultsBar: UIToolbar {
|
||||||
|
|
||||||
|
weak var resultsBarDelegate: SearchResultsBarDelegate?
|
||||||
|
|
||||||
|
var showLessRecentButton: UIBarButtonItem!
|
||||||
|
var showMoreRecentButton: UIBarButtonItem!
|
||||||
|
let labelItem: UIBarButtonItem
|
||||||
|
|
||||||
|
var resultSet: ConversationScreenSearchResultSet?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
|
||||||
|
labelItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
let leftExteriorChevronMargin: CGFloat
|
||||||
|
let leftInteriorChevronMargin: CGFloat
|
||||||
|
if CurrentAppContext().isRTL {
|
||||||
|
leftExteriorChevronMargin = 8
|
||||||
|
leftInteriorChevronMargin = 0
|
||||||
|
} else {
|
||||||
|
leftExteriorChevronMargin = 0
|
||||||
|
leftInteriorChevronMargin = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
let upChevron = #imageLiteral(resourceName: "ic_chevron_up").withRenderingMode(.alwaysTemplate)
|
||||||
|
showLessRecentButton = UIBarButtonItem(image: upChevron, style: .plain, target: self, action: #selector(didTapShowLessRecent))
|
||||||
|
showLessRecentButton.imageInsets = UIEdgeInsets(top: 2, left: leftExteriorChevronMargin, bottom: 2, right: leftInteriorChevronMargin)
|
||||||
|
showLessRecentButton.tintColor = UIColor.ows_systemPrimaryButton
|
||||||
|
|
||||||
|
let downChevron = #imageLiteral(resourceName: "ic_chevron_down").withRenderingMode(.alwaysTemplate)
|
||||||
|
showMoreRecentButton = UIBarButtonItem(image: downChevron, style: .plain, target: self, action: #selector(didTapShowMoreRecent))
|
||||||
|
showMoreRecentButton.imageInsets = UIEdgeInsets(top: 2, left: leftInteriorChevronMargin, bottom: 2, right: leftExteriorChevronMargin)
|
||||||
|
showMoreRecentButton.tintColor = UIColor.ows_systemPrimaryButton
|
||||||
|
|
||||||
|
let spacer1 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||||
|
let spacer2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||||
|
|
||||||
|
self.items = [showLessRecentButton, showMoreRecentButton, spacer1, labelItem, spacer2]
|
||||||
|
|
||||||
|
self.isTranslucent = false
|
||||||
|
self.isOpaque = true
|
||||||
|
self.barTintColor = Theme.toolbarBackgroundColor
|
||||||
|
|
||||||
|
self.autoresizingMask = .flexibleHeight
|
||||||
|
self.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func didTapShowLessRecent() {
|
||||||
|
Logger.debug("")
|
||||||
|
guard let resultSet = resultSet else {
|
||||||
|
owsFailDebug("resultSet was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let currentIndex = currentIndex else {
|
||||||
|
owsFailDebug("currentIndex was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard currentIndex + 1 < resultSet.messages.count else {
|
||||||
|
owsFailDebug("showLessRecent button should be disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let newIndex = currentIndex + 1
|
||||||
|
self.currentIndex = newIndex
|
||||||
|
updateBarItems()
|
||||||
|
resultsBarDelegate?.searchResultsBar(self, setCurrentIndex: newIndex, resultSet: resultSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func didTapShowMoreRecent() {
|
||||||
|
Logger.debug("")
|
||||||
|
guard let resultSet = resultSet else {
|
||||||
|
owsFailDebug("resultSet was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let currentIndex = currentIndex else {
|
||||||
|
owsFailDebug("currentIndex was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard currentIndex > 0 else {
|
||||||
|
owsFailDebug("showMoreRecent button should be disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let newIndex = currentIndex - 1
|
||||||
|
self.currentIndex = newIndex
|
||||||
|
updateBarItems()
|
||||||
|
resultsBarDelegate?.searchResultsBar(self, setCurrentIndex: newIndex, resultSet: resultSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex: Int?
|
||||||
|
|
||||||
|
// MARK:
|
||||||
|
|
||||||
|
func updateResults(resultSet: ConversationScreenSearchResultSet?) {
|
||||||
|
if let resultSet = resultSet {
|
||||||
|
if resultSet.messages.count > 0 {
|
||||||
|
currentIndex = min(currentIndex ?? 0, resultSet.messages.count - 1)
|
||||||
|
} else {
|
||||||
|
currentIndex = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentIndex = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.resultSet = resultSet
|
||||||
|
|
||||||
|
updateBarItems()
|
||||||
|
if let currentIndex = currentIndex, let resultSet = resultSet {
|
||||||
|
resultsBarDelegate?.searchResultsBar(self, setCurrentIndex: currentIndex, resultSet: resultSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateBarItems() {
|
||||||
|
guard let resultSet = resultSet else {
|
||||||
|
labelItem.title = nil
|
||||||
|
showMoreRecentButton.isEnabled = false
|
||||||
|
showLessRecentButton.isEnabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resultSet.messages.count {
|
||||||
|
case 0:
|
||||||
|
labelItem.title = NSLocalizedString("CONVERSATION_SEARCH_NO_RESULTS", comment: "keyboard toolbar label when no messages match the search string")
|
||||||
|
case 1:
|
||||||
|
labelItem.title = NSLocalizedString("CONVERSATION_SEARCH_ONE_RESULT", comment: "keyboard toolbar label when exactly 1 message matches the search string")
|
||||||
|
default:
|
||||||
|
let format = NSLocalizedString("CONVERSATION_SEARCH_RESULTS_FORMAT",
|
||||||
|
comment: "keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}}")
|
||||||
|
|
||||||
|
guard let currentIndex = currentIndex else {
|
||||||
|
owsFailDebug("currentIndex was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labelItem.title = String(format: format, currentIndex + 1, resultSet.messages.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentIndex = currentIndex {
|
||||||
|
showMoreRecentButton.isEnabled = currentIndex > 0
|
||||||
|
showLessRecentButton.isEnabled = currentIndex + 1 < resultSet.messages.count
|
||||||
|
} else {
|
||||||
|
showMoreRecentButton.isEnabled = false
|
||||||
|
showLessRecentButton.isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,13 @@
|
|||||||
//
|
//
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "OWSConversationSettingsViewDelegate.h"
|
|
||||||
#import <SignalMessaging/OWSViewController.h>
|
#import <SignalMessaging/OWSViewController.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface NewGroupViewController : OWSViewController
|
@interface NewGroupViewController : OWSViewController
|
||||||
|
|
||||||
@property (nonatomic, weak) id<OWSConversationSettingsViewDelegate> delegate;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
//
|
//
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface OWSSearchBar : UISearchBar
|
@interface OWSSearchBar : UISearchBar
|
||||||
|
|
||||||
|
+ (void)applyThemeToSearchBar:(UISearchBar *)searchBar;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
Loading…
Reference in New Issue