Replace Search tab with Explore tab

- Search controller (functionally the same, presents results on top of
explore menu)
- Add bookmarks screen

See #63
This commit is contained in:
Shadowfacts 2019-12-17 00:22:25 -05:00
parent 382decd7da
commit 036791bd39
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
11 changed files with 428 additions and 48 deletions

View File

@ -308,6 +308,14 @@ public class Client {
return timeline.request(range: range)
}
// MARK: Bookmarks
public func getBookmarks(range: RequestRange = .default) -> Request<[Status]> {
var request = Request<[Status]>(method: .get, path: "/api/v1/bookmarks")
request.range = range
return request
}
}
extension Client {

View File

@ -81,6 +81,9 @@
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; };
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */; };
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */; };
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943D23A564D400D38C68 /* ExploreViewController.swift */; };
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; };
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */; };
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF75217E923E00CC0648 /* DraftsManager.swift */; };
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF78217E950100CC0648 /* DraftsTableViewController.xib */; };
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; };
@ -184,7 +187,7 @@
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
D6BC9DB5232D4CE3002CA326 /* NotificationsMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB4232D4CE3002CA326 /* NotificationsMode.swift */; };
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
D6BC9DDA232D8BE5002CA326 /* SearchTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */; };
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
@ -349,6 +352,9 @@
D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; };
D627943D23A564D400D38C68 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = "<group>"; };
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; };
D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTableViewController.swift; sourceTree = "<group>"; };
D627FF75217E923E00CC0648 /* DraftsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsManager.swift; sourceTree = "<group>"; };
D627FF78217E950100CC0648 /* DraftsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftsTableViewController.xib; sourceTree = "<group>"; };
D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = "<group>"; };
@ -449,7 +455,7 @@
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
D6BC9DB4232D4CE3002CA326 /* NotificationsMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsMode.swift; sourceTree = "<group>"; };
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTableViewController.swift; sourceTree = "<group>"; };
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = "<group>"; };
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
@ -717,6 +723,22 @@
path = "Status Activities";
sourceTree = "<group>";
};
D627943C23A5635D00D38C68 /* Explore */ = {
isa = PBXGroup;
children = (
D627943D23A564D400D38C68 /* ExploreViewController.swift */,
);
path = Explore;
sourceTree = "<group>";
};
D627944823A6AD5100D38C68 /* Bookmarks */ = {
isa = PBXGroup;
children = (
D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */,
);
path = Bookmarks;
sourceTree = "<group>";
};
D627FF77217E94F200CC0648 /* Drafts */ = {
isa = PBXGroup;
children = (
@ -750,12 +772,14 @@
D641C785213DD83B004B4513 /* Conversation */,
D641C786213DD852004B4513 /* Notifications */,
D641C787213DD862004B4513 /* Compose */,
D627943C23A5635D00D38C68 /* Explore */,
D6BC9DD8232D8BCA002CA326 /* Search */,
D641C788213DD86D004B4513 /* Large Image */,
0411610422B4571E0030A9B7 /* Attachment */,
0411610522B457290030A9B7 /* Gallery */,
D6A3BC822321F69400FD64D5 /* Account List */,
D6A3BC8C2321FF9B00FD64D5 /* Status Action Account List */,
D627944823A6AD5100D38C68 /* Bookmarks */,
D641C789213DD87E004B4513 /* Preferences */,
);
path = Screens;
@ -1082,7 +1106,7 @@
D6BC9DD8232D8BCA002CA326 /* Search */ = {
isa = PBXGroup;
children = (
D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */,
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */,
);
path = Search;
sourceTree = "<group>";
@ -1097,6 +1121,7 @@
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
D67C57A721E2649B00C3118B /* Account Detail */,
D67C57B021E28F9400C3118B /* Compose Status Reply */,
D60C07E221E817560057FAA8 /* Compose Media */,
@ -1481,6 +1506,7 @@
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
@ -1622,7 +1648,7 @@
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */,
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
D6BC9DDA232D8BE5002CA326 /* SearchTableViewController.swift in Sources */,
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
@ -1630,6 +1656,7 @@
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
@ -1640,6 +1667,7 @@
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,

View File

@ -40,19 +40,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return XCBManager.handle(url: url)
} else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let tabBarController = window!.rootViewController as? MainTabBarViewController,
let navigationController = tabBarController.viewControllers?[3] as? UINavigationController,
let searchController = navigationController.viewControllers.first as? SearchTableViewController {
let exploreNavController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
let exploreController = exploreNavController.viewControllers.first as? ExploreViewController {
tabBarController.select(tab: .explore)
exploreNavController.popToRootViewController(animated: false)
exploreController.loadViewIfNeeded()
exploreController.searchController.isActive = true
components.scheme = "https"
tabBarController.selectedIndex = 3
navigationController.popToRootViewController(animated: false)
searchController.loadViewIfNeeded()
let query = components.url!.absoluteString
searchController.searchController.searchBar.text = query
searchController.performSearch(query: query)
exploreController.searchController.searchBar.text = query
exploreController.resultsController.performSearch(query: query)
return true
}

View File

@ -0,0 +1,172 @@
//
// BookmarksTableViewController.swift
// Tusker
//
// Created by Shadowfacts on 12/15/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class BookmarksTableViewController: EnhancedTableViewController {
private let statusCell = "statusCell"
var statuses: [(id: String, state: StatusState)] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var newer: RequestRange?
var older: RequestRange?
init() {
super.init(style: .plain)
title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 140
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell)
tableView.prefetchDataSource = self
let request = MastodonController.client.getBookmarks()
MastodonController.client.run(request) { (response) in
guard case let .success(statuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses)
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
self.newer = pagination?.newer
self.older = pagination?.older
}
userActivity = UserActivityManager.bookmarksActivity()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return statuses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! TimelineStatusTableViewCell
cell.delegate = self
let (id, state) = statuses[indexPath.row]
cell.updateUI(statusID: id, state: state)
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard indexPath.row == statuses.count, let older = older else {
return
}
let request = MastodonController.client.getBookmarks(range: older)
MastodonController.client.run(request) { (response) in
guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.older = pagination?.older
MastodonCache.addAll(statuses: newStatuses)
self.statuses.append(contentsOf: newStatuses.map { ($0.id, .unknown) })
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.leadingSwipeActionsConfiguration()
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let cellConfig = (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else {
return cellConfig
}
let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in
let request = Status.unbookmark(status)
MastodonController.client.run(request) { (response) in
guard case let .success(newStatus, _) = response else { fatalError() }
MastodonCache.add(status: newStatus)
self.statuses.remove(at: indexPath.row)
}
}
unbookmarkAction.image = UIImage(systemName: "bookmark.fill")
let config: UISwipeActionsConfiguration
if let cellConfig = cellConfig {
config = UISwipeActionsConfiguration(actions: cellConfig.actions + [unbookmarkAction])
config.performsFirstActionWithFullSwipe = cellConfig.performsFirstActionWithFullSwipe
} else {
config = UISwipeActionsConfiguration(actions: [unbookmarkAction])
config.performsFirstActionWithFullSwipe = false
}
return config
}
override func getSuggestedContextMenuActions(tableView: UITableView, indexPath: IndexPath, point: CGPoint) -> [UIAction] {
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { return [] }
return [
UIAction(title: NSLocalizedString("Unbookmark", comment: "unbookmark action title"), image: UIImage(systemName: "bookmark.fill"), identifier: .init("unbookmark"), discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
let request = Status.unbookmark(status)
MastodonController.client.run(request) { (response) in
guard case let .success(newStatus, _) = response else { fatalError() }
MastodonCache.add(status: newStatus)
self.statuses.remove(at: indexPath.row)
}
})
]
}
}
extension BookmarksTableViewController: StatusTableViewCellDelegate {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates()
tableView.endUpdates()
}
}
extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
ImageCache.avatars.get(status.account.avatar, completion: nil)
for attachment in status.attachments where attachment.kind == .image {
ImageCache.attachments.get(attachment.url, completion: nil)
}
}
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
ImageCache.avatars.cancel(status.account.avatar)
for attachment in status.attachments where attachment.kind == .image {
ImageCache.attachments.cancel(attachment.url)
}
}
}
}

View File

@ -0,0 +1,111 @@
//
// ExploreViewController.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Combine
class ExploreViewController: EnhancedTableViewController {
var dataSource: UITableViewDiffableDataSource<Section, Item>!
var resultsController: SearchResultsViewController!
var searchController: UISearchController!
let searchSubject = PassthroughSubject<String?, Never>()
init() {
super.init(style: .insetGrouped)
title = NSLocalizedString("Explore", comment: "explore tab title")
tabBarItem.image = UIImage(systemName: "magnifyingglass")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: "basicCell")
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
switch item {
case .bookmarks:
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
cell.imageView!.image = UIImage(systemName: "bookmark.fill")
cell.textLabel!.text = NSLocalizedString("Bookmarks", comment: "bookmarks nav item title")
cell.accessoryType = .disclosureIndicator
return cell
case let .list(id):
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
cell.imageView!.image = nil
cell.textLabel!.text = id
cell.accessoryType = .disclosureIndicator
return cell
}
})
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.bookmarks, .lists])
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
// the initial, static items should not be displayed with an animation
UIView.performWithoutAnimation {
dataSource.apply(snapshot)
}
resultsController = SearchResultsViewController()
resultsController.exploreNavigationController = self.navigationController!
searchController = UISearchController(searchResultsController: resultsController)
searchController.searchResultsUpdater = resultsController
searchController.searchBar.autocapitalizationType = .none
searchController.searchBar.delegate = resultsController
definesPresentationContext = true
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch dataSource.itemIdentifier(for: indexPath) {
case nil:
return
case .bookmarks:
show(BookmarksTableViewController(), sender: nil)
case let .list(id):
show(TimelineTableViewController(for: .list(id: id)), sender: nil)
}
}
}
extension ExploreViewController {
enum Section: CaseIterable {
case bookmarks
case lists
}
enum Item: Hashable {
case bookmarks
case list(id: String)
}
class DataSource: UITableViewDiffableDataSource<Section, Item> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 1:
return NSLocalizedString("Lists", comment: "explore lists section title")
default:
return nil
}
}
}
}

View File

@ -19,7 +19,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
embedInNavigationController(TimelinesPageViewController()),
embedInNavigationController(NotificationsPageViewController()),
ComposeViewController(),
embedInNavigationController(SearchTableViewController()),
embedInNavigationController(ExploreViewController()),
embedInNavigationController(MyProfileTableViewController()),
]
}
@ -53,7 +53,7 @@ extension MainTabBarViewController {
case timelines
case notifications
case compose
case search
case explore
case myProfile
}

View File

@ -1,5 +1,5 @@
//
// SearchTableViewController.swift
// SearchResultsViewController.swift
// Tusker
//
// Created by Shadowfacts on 9/14/19.
@ -14,11 +14,12 @@ fileprivate let accountCell = "accountCell"
fileprivate let statusCell = "statusCell"
fileprivate let hashtagCell = "hashtagCell"
class SearchTableViewController: EnhancedTableViewController {
class SearchResultsViewController: EnhancedTableViewController {
var dataSource: UITableViewDiffableDataSource<Section, Item>!
let searchController = UISearchController(searchResultsController: nil)
weak var exploreNavigationController: UINavigationController?
var dataSource: UITableViewDiffableDataSource<Section, Item>!
var activityIndicator: UIActivityIndicatorView!
let searchSubject = PassthroughSubject<String?, Never>()
@ -27,8 +28,7 @@ class SearchTableViewController: EnhancedTableViewController {
init() {
super.init(style: .grouped)
title = NSLocalizedString("Search", comment: "search tab title")
tabBarItem.image = UIImage(systemName: "magnifyingglass")
title = NSLocalizedString("Search", comment: "search screen title")
}
required init?(coder: NSCoder) {
@ -62,13 +62,6 @@ class SearchTableViewController: EnhancedTableViewController {
}
})
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = NSLocalizedString("Search or Enter URL", comment: "search field placeholder")
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
definesPresentationContext = true
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isHidden = true
@ -83,33 +76,43 @@ class SearchTableViewController: EnhancedTableViewController {
.map { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0 != self.currentQuery }
.sink(receiveValue: performSearch(query:))
userActivity = UserActivityManager.searchActivity()
}
override func targetViewController(forAction action: Selector, sender: Any?) -> UIViewController? {
// if we're showing a view controller, we need to go up to the explore VC's nav controller
// the UISearchController that is our parent is not part of the normal VC hierarchy and itself doesn't have a parent
if action == #selector(UIViewController.show(_:sender:)),
let exploreNavController = exploreNavigationController {
return exploreNavController
}
return super.targetViewController(forAction: action, sender: sender)
}
func performSearch(query: String?) {
guard let query = query, !query.isEmpty else {
self.dataSource.apply(NSDiffableDataSourceSnapshot<Section, Item>())
return
}
self.currentQuery = query
if self.dataSource.snapshot().numberOfItems == 0 {
activityIndicator.isHidden = false
activityIndicator.startAnimating()
}
let request = MastodonController.client.search(query: query, resolve: true, limit: 10)
MastodonController.client.run(request) { (response) in
guard case let .success(results, _) = response else { fatalError() }
DispatchQueue.main.async {
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
}
guard self.currentQuery == query else { return }
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
if !results.accounts.isEmpty {
snapshot.appendSections([.accounts])
@ -132,7 +135,7 @@ class SearchTableViewController: EnhancedTableViewController {
}
extension SearchTableViewController {
extension SearchResultsViewController {
enum Section: CaseIterable {
case accounts
case hashtags
@ -166,20 +169,20 @@ extension SearchTableViewController {
}
}
extension SearchTableViewController: UISearchResultsUpdating {
extension SearchResultsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
searchSubject.send(searchController.searchBar.text)
}
}
extension SearchTableViewController: UISearchBarDelegate {
extension SearchResultsViewController: UISearchBarDelegate {
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
// perform a search immedaitely when the search button is pressed
performSearch(query: searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines))
}
}
extension SearchTableViewController: StatusTableViewCellDelegate {
extension SearchResultsViewController: StatusTableViewCellDelegate {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates()
tableView.endUpdates()

View File

@ -145,8 +145,8 @@ class UserActivityManager {
}
}
// MARK: - Search
// MARK: - Explore
static func searchActivity() -> NSUserActivity {
let activity = NSUserActivity(type: .search)
activity.isEligibleForPrediction = true
@ -157,9 +157,29 @@ class UserActivityManager {
static func handleSearch(activity: NSUserActivity) {
let tabBarController = getMainTabBarController()
tabBarController.select(tab: .search)
if let navigationController = tabBarController.getTabController(tab: .search) as? UINavigationController {
tabBarController.select(tab: .explore)
if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
let exploreController = navigationController.viewControllers.first as? ExploreViewController {
navigationController.popToRootViewController(animated: false)
exploreController.searchController.isActive = true
exploreController.searchController.searchBar.becomeFirstResponder()
}
}
static func bookmarksActivity() -> NSUserActivity {
let activity = NSUserActivity(type: .bookmarks)
activity.isEligibleForPrediction = true
activity.title = NSLocalizedString("View Bookmarks", comment: "bookmarks shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Show my bookmarks in Tusker", comment: "bookmarks shortcut invocation phrase")
return activity
}
static func handleBookmarks(activity: NSUserActivity) {
let tabBarController = getMainTabBarController()
tabBarController.select(tab: .explore)
if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController {
navigationController.popToRootViewController(animated: false)
navigationController.pushViewController(BookmarksTableViewController(), animated: false)
}
}

View File

@ -14,6 +14,7 @@ enum UserActivityType: String {
case checkMentions = "net.shadowfacts.Tusker.activity.check-mentions"
case showTimeline = "net.shadowfacts.Tusker.activity.show-timeline"
case search = "net.shadowfacts.Tusker.activity.search"
case bookmarks = "net.shadowfacts.Tusker.activity.bookmarks"
}
extension UserActivityType {
@ -29,6 +30,8 @@ extension UserActivityType {
return UserActivityManager.handleShowTimeline
case .search:
return UserActivityManager.handleSearch
case .bookmarks:
return UserActivityManager.handleBookmarks
}
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15703"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="xP3-ps-Bp7" style="IBUITableViewCellStyleDefault" id="QwQ-aK-6Xu">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QwQ-aK-6Xu" id="whG-vX-EQq">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xP3-ps-Bp7">
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<point key="canvasLocation" x="-113" y="20"/>
</tableViewCell>
</objects>
</document>

View File

@ -329,12 +329,17 @@ struct XCBActions {
let query = request.arguments["query"]!
let tabBarController = getMainTabBarController()
if let navigationController = tabBarController.getTabController(tab: .search) as? UINavigationController,
let searchController = navigationController.viewControllers.first as? SearchTableViewController {
tabBarController.select(tab: .search)
if let navigationController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
let exploreController = navigationController.viewControllers.first as? ExploreViewController {
tabBarController.select(tab: .explore)
navigationController.popToRootViewController(animated: false)
searchController.searchController.searchBar.text = query
searchController.performSearch(query: query)
exploreController.loadViewIfNeeded()
exploreController.searchController.isActive = true
exploreController.searchController.searchBar.text = query
exploreController.resultsController.performSearch(query: query)
} else {
session.complete(with: .error)
}