172 lines
6.6 KiB
Swift
172 lines
6.6 KiB
Swift
//
|
|
// SearchViewController.swift
|
|
// Mastodon
|
|
//
|
|
// Created by sxiaojian on 2021/3/31.
|
|
//
|
|
|
|
import os.log
|
|
import Combine
|
|
import GameplayKit
|
|
import MastodonSDK
|
|
import UIKit
|
|
import MastodonAsset
|
|
import MastodonLocalization
|
|
|
|
final class HeightFixedSearchBar: UISearchBar {
|
|
override var intrinsicContentSize: CGSize {
|
|
return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44)
|
|
}
|
|
}
|
|
|
|
final class SearchViewController: UIViewController, NeedsDependency {
|
|
|
|
let logger = Logger(subsystem: "SearchViewController", category: "ViewController")
|
|
|
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
|
|
|
var searchTransitionController = SearchTransitionController()
|
|
|
|
var disposeBag = Set<AnyCancellable>()
|
|
private(set) lazy var viewModel = SearchViewModel(context: context)
|
|
|
|
// use AutoLayout could set search bar margin automatically to
|
|
// layout alongside with split mode button (on iPad)
|
|
let titleViewContainer = UIView()
|
|
let searchBar = HeightFixedSearchBar()
|
|
|
|
let collectionView: UICollectionView = {
|
|
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
|
configuration.backgroundColor = .clear
|
|
configuration.headerMode = .supplementary
|
|
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
|
|
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
collectionView.backgroundColor = .clear
|
|
return collectionView
|
|
}()
|
|
|
|
let searchBarTapPublisher = PassthroughSubject<Void, Never>()
|
|
|
|
deinit {
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
}
|
|
|
|
}
|
|
|
|
extension SearchViewController {
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
|
ThemeService.shared.currentTheme
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] theme in
|
|
guard let self = self else { return }
|
|
self.setupBackgroundColor(theme: theme)
|
|
}
|
|
.store(in: &disposeBag)
|
|
|
|
title = L10n.Scene.Search.title
|
|
|
|
setupSearchBar()
|
|
|
|
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
|
view.addSubview(collectionView)
|
|
NSLayoutConstraint.activate([
|
|
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
])
|
|
|
|
collectionView.delegate = self
|
|
viewModel.setupDiffableDataSource(
|
|
collectionView: collectionView
|
|
)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
viewModel.viewDidAppeared.send()
|
|
|
|
// note:
|
|
// need set alpha because (maybe) SDK forget set alpha back
|
|
titleViewContainer.alpha = 1
|
|
}
|
|
}
|
|
|
|
extension SearchViewController {
|
|
private func setupBackgroundColor(theme: Theme) {
|
|
view.backgroundColor = theme.systemGroupedBackgroundColor
|
|
}
|
|
|
|
private func setupSearchBar() {
|
|
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
|
searchBar.delegate = self
|
|
searchBar.translatesAutoresizingMaskIntoConstraints = false
|
|
titleViewContainer.addSubview(searchBar)
|
|
NSLayoutConstraint.activate([
|
|
searchBar.topAnchor.constraint(equalTo: titleViewContainer.topAnchor),
|
|
searchBar.leadingAnchor.constraint(equalTo: titleViewContainer.leadingAnchor),
|
|
searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor),
|
|
searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor),
|
|
])
|
|
navigationItem.titleView = titleViewContainer
|
|
|
|
searchBarTapPublisher
|
|
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false)
|
|
.sink { [weak self] in
|
|
guard let self = self else { return }
|
|
// push to search detail
|
|
let searchDetailViewModel = SearchDetailViewModel()
|
|
searchDetailViewModel.needsBecomeFirstResponder = true
|
|
self.navigationController?.delegate = self.searchTransitionController
|
|
self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush)
|
|
}
|
|
.store(in: &disposeBag)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - UISearchBarDelegate
|
|
extension SearchViewController: UISearchBarDelegate {
|
|
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
searchBarTapPublisher.send()
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MARK: - UISearchControllerDelegate
|
|
extension SearchViewController: UISearchControllerDelegate {
|
|
func willDismissSearchController(_ searchController: UISearchController) {
|
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
searchController.isActive = true
|
|
}
|
|
func didPresentSearchController(_ searchController: UISearchController) {
|
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDelegate
|
|
extension SearchViewController: UICollectionViewDelegate {
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)")
|
|
|
|
defer {
|
|
collectionView.deselectItem(at: indexPath, animated: true)
|
|
}
|
|
|
|
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
|
|
|
switch item {
|
|
case .trend(let hashtag):
|
|
let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name)
|
|
coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show)
|
|
}
|
|
}
|
|
}
|