X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/WolneLektury/Screens/Search/SearchViewController.swift?ds=sidebyside diff --git a/iOS/WolneLektury/Screens/Search/SearchViewController.swift b/iOS/WolneLektury/Screens/Search/SearchViewController.swift new file mode 100644 index 0000000..d2ff84e --- /dev/null +++ b/iOS/WolneLektury/Screens/Search/SearchViewController.swift @@ -0,0 +1,349 @@ +// +// 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) + } +}