--- /dev/null
+//
+// SearchViewController.swift
+// WolneLektury
+//
+// Created by Pawel Dabrowski on 13/06/2018.
+// Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
+//
+
+import UIKit
+import Toast_Swift
+
+enum SearchViewControllerType{
+ case search
+}
+
+protocol DataLoaderDelegate: class{
+ func dataLoaderFetchingServerFinished()
+ func dataLoaderFetchingServerSuccess()
+ func dataLoaderFetchingServerFailed(showRefreshButton: Bool)
+}
+
+class DataLoader: NSObject{
+ var dataSource = [BookModel]()
+ var loadingMore = false
+ var canLoadMore = false
+ var currentParams = FilterBooksParameters()
+ weak var delegate: DataLoaderDelegate?
+
+ init(delegate: DataLoaderDelegate) {
+ self.delegate = delegate
+ }
+
+ func setNewFilters(filterParams: FilterBooksParameters) {
+ let newParams = filterParams
+ newParams.search = currentParams.search
+ currentParams = newParams
+ dataSource = [BookModel]()
+ }
+
+ func loadData(more: Bool){
+
+ let params = currentParams
+ if more{
+ loadingMore = true
+ currentParams.after = dataSource.last?.key
+ }
+
+ appDelegate.syncManager.filterBooks(params: params) { [weak self] (result) in
+
+ guard let strongSelf = self else{
+ return
+ }
+
+ strongSelf.delegate?.dataLoaderFetchingServerFinished()
+ strongSelf.loadingMore = false
+
+ guard strongSelf.currentParams == params else{
+ print("strongSelf.currentParams != cp")
+ return
+ }
+
+ switch result {
+ case .success(let model):
+
+ let array = model as! [BookModel]
+ strongSelf.canLoadMore = array.count == FilterBooksParameters.SEARCH_ITEMS_COUNT
+ strongSelf.dataSource.append(contentsOf: array)
+ strongSelf.delegate?.dataLoaderFetchingServerSuccess()
+
+
+ case .failure(let error):
+ if strongSelf.dataSource.count > 0{
+ strongSelf.canLoadMore = false
+ strongSelf.delegate?.dataLoaderFetchingServerFailed(showRefreshButton: false)
+ }
+ else{
+ strongSelf.delegate?.dataLoaderFetchingServerFailed(showRefreshButton: true)
+ }
+ break
+ }
+ }
+ }
+}
+
+class SearchViewController: MainViewController {
+
+ @IBOutlet weak var filterCloseButton: UIButton!
+ @IBOutlet weak var filterContainer: UIView!
+ @IBOutlet weak var filterCollectionView: UICollectionView!
+ @IBOutlet weak var filterTopConstraint: NSLayoutConstraint!
+ @IBOutlet weak var tableView: UITableView!
+ @IBOutlet weak var footerViewActivityIndicator: UIActivityIndicatorView!
+ @IBOutlet weak var noDataLabel: UILabel!
+ @IBOutlet weak var refreshDataButton: ActivityIndicatorButton!
+
+ var dataLoader: DataLoader!
+ var filtersManager : SearchFiltersManager!
+
+ var controllerType = SearchViewControllerType.search
+
+ lazy var searchBar = UISearchBar(frame: CGRect.zero)
+ var currentSearchQuery = ""
+
+ class func instance(type: SearchViewControllerType) -> SearchViewController{
+ let controller = SearchViewController.instance()
+ controller.controllerType = type
+ controller.dataLoader = DataLoader(delegate: controller)
+ return controller
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ title = "nav_search".localized
+
+ filterCloseButton.layer.cornerRadius = 14
+ filterCloseButton.tintColor = Constants.Colors.darkGreenBgColor()
+
+ noDataLabel.text = "no_results".localized
+ noDataLabel.isHidden = true
+
+ refreshDataButton.tintColor = .black
+
+ filtersManager = SearchFiltersManager(delegate: self)
+ setupTableView()
+ setupCollectionView()
+ filtersChanged(initially: true, reloadData: true)
+ setupSearchBar()
+// searchBar.becomeFirstResponder()
+ }
+
+ func setupSearchBar() {
+ searchBar.placeholder = "search_placeholder".localized
+ searchBar.enablesReturnKeyAutomatically = false
+ UITextField.appearance(whenContainedInInstancesOf: [type(of: searchBar)]).tintColor = UIColor.gray
+ searchBar.delegate = self
+ searchBar.translatesAutoresizingMaskIntoConstraints = false
+
+ searchBar.layoutIfNeeded()
+ searchBar.sizeToFit()
+ searchBar.translatesAutoresizingMaskIntoConstraints = true // make nav bar happy
+
+ navigationItem.titleView = searchBar
+// if #available(iOS 11.0, *) {
+//// searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
+// }
+ let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(sender:)))
+ singleTapGestureRecognizer.numberOfTapsRequired = 1
+ singleTapGestureRecognizer.isEnabled = true
+ singleTapGestureRecognizer.cancelsTouchesInView = false
+ self.view.addGestureRecognizer(singleTapGestureRecognizer)
+ }
+
+ func setupTableView(){
+ tableView.registerNib(name: "BookTableViewCell")
+ tableView.separatorStyle = .none
+ tableView.delegate = self
+ tableView.dataSource = self
+ tableView.rowHeight = 137
+ var insets = tableView.contentInset
+ insets.top = 11
+ tableView.contentInset = insets
+ footerViewActivityIndicator.color = Constants.Colors.darkGreenBgColor()
+ footerViewActivityIndicator.hidesWhenStopped = true
+ }
+
+ @objc func singleTap(sender: UITapGestureRecognizer) {
+ self.searchBar.resignFirstResponder()
+ }
+
+ @IBAction func filterCloseButtonAction(_ sender: Any) {
+ filtersManager.clearFilters()
+ }
+
+ @IBAction func refreshDataButtonAction(_ sender: Any) {
+ loadData(more: false, fromRefreshControl: false)
+ }
+
+ func setupCollectionView(){
+
+ filterCollectionView.backgroundColor = UIColor.clear
+ filterCollectionView.delegate = filtersManager
+ filterCollectionView.dataSource = filtersManager
+ filterCollectionView.register(UINib.init(nibName: "SearchFilterCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "SearchFilterCollectionViewCell")
+ }
+
+ func filtersChanged(initially: Bool = false, reloadData: Bool){
+ if reloadData{
+ filterCollectionView.reloadData()
+
+ if filtersManager.numberOfFilters() == 0{
+ if filterTopConstraint.constant != 0 && !initially{
+ filterTopConstraint.constant = 0
+
+ UIView.animate(withDuration:0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [], animations: {
+ self.view.layoutIfNeeded()
+ }, completion: {
+ [weak self] (value: Bool) in
+ self?.filterContainer.isHidden = true
+ })
+ }
+ else{
+ filterTopConstraint.constant = 0
+ filterContainer.isHidden = true
+ }
+ }
+ else{
+ filterContainer.isHidden = false
+ filterTopConstraint.constant = 44
+ }
+ }
+
+ dataLoader.setNewFilters(filterParams: filtersManager.getParametersForApi())
+
+ tableView.reloadData()
+
+ loadData(more: false, fromRefreshControl: false)
+ }
+
+ func loadData(more: Bool, fromRefreshControl: Bool){
+
+ refreshDataButton.setIndicatorButtonState(state: .hidden)
+ if more && dataLoader.loadingMore{
+ return
+ }
+
+ if !fromRefreshControl{
+ footerViewActivityIndicator.startAnimating()
+ }
+
+ dataLoader.loadData(more: more)
+ }
+
+ @IBAction func filterButtonAction(_ sender: Any) {
+ self.navigationController?.present( WLNavigationController(rootViewController: FilterViewController.instance(delegate: self, selectedKindsArray: filtersManager.selectedKindsArray, selectedEpochsArray: filtersManager.selectedEpochsArray, selectedGenresArray: filtersManager.selectedGenresArray, onlyLectures: filtersManager.onlyLecturesCategory.checked, hasAudiobook: filtersManager.hasAudiobookCategory.checked)) , animated: true, completion: nil)
+ }
+
+ @objc func getDataForCurrentSearchQuery(){
+ if currentSearchQuery.count > 0 {
+ dataLoader.currentParams.search = currentSearchQuery
+ }
+ else{
+ dataLoader.currentParams.search = nil
+ }
+ dataLoader.dataSource = [BookModel]()
+ tableView.reloadData()
+ loadData(more: false, fromRefreshControl: false)
+ }
+}
+
+extension SearchViewController: UITableViewDataSource{
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
+ return dataLoader.dataSource.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "BookTableViewCell", for: indexPath) as! BookTableViewCell
+ cell.setup(bookModel: dataLoader.dataSource[indexPath.row])
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
+ if indexPath.row > 0 && indexPath.row == (dataLoader.dataSource.count-1) && dataLoader.canLoadMore && !dataLoader.loadingMore{
+ let _ = loadData(more: true, fromRefreshControl: false)
+ }
+ }
+}
+
+extension SearchViewController: UITableViewDelegate{
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ tableView.deselectRow(at: indexPath, animated: true)
+ searchBar.resignFirstResponder()
+ if dataLoader.dataSource.count > indexPath.row{
+ navigationController?.pushViewController(BookDetailsViewController.instance(bookSlug: dataLoader.dataSource[indexPath.row].slug) , animated: true)
+ }
+ }
+}
+
+extension SearchViewController: FilterViewControllerDelegate{
+ func filterViewControllerDidSelectItems(kindsArray: [CategoryModel], epochsArray: [CategoryModel], genresArray: [CategoryModel], onlyLectures: Bool, hasAudiobook: Bool, filterChanged: Bool) {
+
+ if filterChanged{
+ filtersManager.selectedKindsArray = kindsArray
+ filtersManager.selectedEpochsArray = epochsArray
+ filtersManager.selectedGenresArray = genresArray
+ filtersManager.onlyLecturesCategory.checked = onlyLectures
+ filtersManager.hasAudiobookCategory.checked = hasAudiobook
+
+ filtersChanged(reloadData: true)
+ }
+ self.navigationController?.presentedViewController?.dismiss(animated: true, completion: nil)
+ }
+}
+
+extension SearchViewController: UISearchBarDelegate{
+
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {// called when 'return' key pressed. return NO to ignore.
+ textField.resignFirstResponder()
+ return true
+ }
+
+ func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
+ var searchQueryCandidate = ""
+ if let txt = searchBar.text{
+ searchQueryCandidate = txt.trimmed
+ }
+
+ if(currentSearchQuery != searchQueryCandidate){
+ currentSearchQuery = searchQueryCandidate
+ getDataForCurrentSearchQuery()
+ }
+ }
+
+ func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
+ searchBar.resignFirstResponder()
+ }
+
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+ currentSearchQuery = searchText.trimmed
+
+ NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(SearchViewController.getDataForCurrentSearchQuery), object: nil)
+ self.perform(#selector(SearchViewController.getDataForCurrentSearchQuery), with: nil, afterDelay: 0.8)
+ }
+}
+
+extension SearchViewController: SearchFiltersManagerDelegate{
+ func searchFiltersManagerFiltersChanged(reloadData: Bool) {
+ filtersChanged(reloadData: reloadData)
+ }
+}
+
+extension SearchViewController: DataLoaderDelegate{
+ func dataLoaderFetchingServerFinished() {
+ footerViewActivityIndicator.stopAnimating()
+ }
+
+ func dataLoaderFetchingServerSuccess() {
+ noDataLabel.isHidden = dataLoader.dataSource.count > 0
+ tableView.reloadData()
+ }
+
+ func dataLoaderFetchingServerFailed(showRefreshButton: Bool) {
+
+ if showRefreshButton{
+ refreshDataButton.setIndicatorButtonState(state: .button)
+ }
+ view.makeToast("book_loading_error".localized, duration: 3.0, position: .bottom)
+ }
+}