2 // SearchViewController.swift
5 // Created by Pawel Dabrowski on 13/06/2018.
6 // Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
12 enum SearchViewControllerType{
16 protocol DataLoaderDelegate: class{
17 func dataLoaderFetchingServerFinished()
18 func dataLoaderFetchingServerSuccess()
19 func dataLoaderFetchingServerFailed(showRefreshButton: Bool)
22 class DataLoader: NSObject{
23 var dataSource = [BookModel]()
24 var loadingMore = false
25 var canLoadMore = false
26 var currentParams = FilterBooksParameters()
27 weak var delegate: DataLoaderDelegate?
29 init(delegate: DataLoaderDelegate) {
30 self.delegate = delegate
33 func setNewFilters(filterParams: FilterBooksParameters) {
34 let newParams = filterParams
35 newParams.search = currentParams.search
36 currentParams = newParams
37 dataSource = [BookModel]()
40 func loadData(more: Bool){
42 let params = currentParams
45 currentParams.after = dataSource.last?.key
48 appDelegate.syncManager.filterBooks(params: params) { [weak self] (result) in
50 guard let strongSelf = self else{
54 strongSelf.delegate?.dataLoaderFetchingServerFinished()
55 strongSelf.loadingMore = false
57 guard strongSelf.currentParams == params else{
58 print("strongSelf.currentParams != cp")
63 case .success(let model):
65 let array = model as! [BookModel]
66 strongSelf.canLoadMore = array.count == FilterBooksParameters.SEARCH_ITEMS_COUNT
67 strongSelf.dataSource.append(contentsOf: array)
68 strongSelf.delegate?.dataLoaderFetchingServerSuccess()
71 case .failure(let error):
72 if strongSelf.dataSource.count > 0{
73 strongSelf.canLoadMore = false
74 strongSelf.delegate?.dataLoaderFetchingServerFailed(showRefreshButton: false)
77 strongSelf.delegate?.dataLoaderFetchingServerFailed(showRefreshButton: true)
85 class SearchViewController: MainViewController {
87 @IBOutlet weak var filterCloseButton: UIButton!
88 @IBOutlet weak var filterContainer: UIView!
89 @IBOutlet weak var filterCollectionView: UICollectionView!
90 @IBOutlet weak var filterTopConstraint: NSLayoutConstraint!
91 @IBOutlet weak var tableView: UITableView!
92 @IBOutlet weak var footerViewActivityIndicator: UIActivityIndicatorView!
93 @IBOutlet weak var noDataLabel: UILabel!
94 @IBOutlet weak var refreshDataButton: ActivityIndicatorButton!
96 var dataLoader: DataLoader!
97 var filtersManager : SearchFiltersManager!
99 var controllerType = SearchViewControllerType.search
101 lazy var searchBar = UISearchBar(frame: CGRect.zero)
102 var currentSearchQuery = ""
104 class func instance(type: SearchViewControllerType) -> SearchViewController{
105 let controller = SearchViewController.instance()
106 controller.controllerType = type
107 controller.dataLoader = DataLoader(delegate: controller)
111 override func viewDidLoad() {
113 title = "nav_search".localized
115 filterCloseButton.layer.cornerRadius = 14
116 filterCloseButton.tintColor = Constants.Colors.darkGreenBgColor()
118 noDataLabel.text = "no_results".localized
119 noDataLabel.isHidden = true
121 refreshDataButton.tintColor = .black
123 filtersManager = SearchFiltersManager(delegate: self)
125 setupCollectionView()
126 filtersChanged(initially: true, reloadData: true)
128 // searchBar.becomeFirstResponder()
131 func setupSearchBar() {
132 searchBar.placeholder = "search_placeholder".localized
133 searchBar.enablesReturnKeyAutomatically = false
134 UITextField.appearance(whenContainedInInstancesOf: [type(of: searchBar)]).tintColor = UIColor.gray
135 searchBar.delegate = self
136 searchBar.translatesAutoresizingMaskIntoConstraints = false
138 searchBar.layoutIfNeeded()
139 searchBar.sizeToFit()
140 searchBar.translatesAutoresizingMaskIntoConstraints = true // make nav bar happy
142 navigationItem.titleView = searchBar
143 // if #available(iOS 11.0, *) {
144 //// searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
146 let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(sender:)))
147 singleTapGestureRecognizer.numberOfTapsRequired = 1
148 singleTapGestureRecognizer.isEnabled = true
149 singleTapGestureRecognizer.cancelsTouchesInView = false
150 self.view.addGestureRecognizer(singleTapGestureRecognizer)
153 func setupTableView(){
154 tableView.registerNib(name: "BookTableViewCell")
155 tableView.separatorStyle = .none
156 tableView.delegate = self
157 tableView.dataSource = self
158 tableView.rowHeight = 137
159 var insets = tableView.contentInset
161 tableView.contentInset = insets
162 footerViewActivityIndicator.color = Constants.Colors.darkGreenBgColor()
163 footerViewActivityIndicator.hidesWhenStopped = true
166 @objc func singleTap(sender: UITapGestureRecognizer) {
167 self.searchBar.resignFirstResponder()
170 @IBAction func filterCloseButtonAction(_ sender: Any) {
171 filtersManager.clearFilters()
174 @IBAction func refreshDataButtonAction(_ sender: Any) {
175 loadData(more: false, fromRefreshControl: false)
178 func setupCollectionView(){
180 filterCollectionView.backgroundColor = UIColor.clear
181 filterCollectionView.delegate = filtersManager
182 filterCollectionView.dataSource = filtersManager
183 filterCollectionView.register(UINib.init(nibName: "SearchFilterCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "SearchFilterCollectionViewCell")
186 func filtersChanged(initially: Bool = false, reloadData: Bool){
188 filterCollectionView.reloadData()
190 if filtersManager.numberOfFilters() == 0{
191 if filterTopConstraint.constant != 0 && !initially{
192 filterTopConstraint.constant = 0
194 UIView.animate(withDuration:0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [], animations: {
195 self.view.layoutIfNeeded()
197 [weak self] (value: Bool) in
198 self?.filterContainer.isHidden = true
202 filterTopConstraint.constant = 0
203 filterContainer.isHidden = true
207 filterContainer.isHidden = false
208 filterTopConstraint.constant = 44
212 dataLoader.setNewFilters(filterParams: filtersManager.getParametersForApi())
214 tableView.reloadData()
216 loadData(more: false, fromRefreshControl: false)
219 func loadData(more: Bool, fromRefreshControl: Bool){
221 refreshDataButton.setIndicatorButtonState(state: .hidden)
222 if more && dataLoader.loadingMore{
226 if !fromRefreshControl{
227 footerViewActivityIndicator.startAnimating()
230 dataLoader.loadData(more: more)
233 @IBAction func filterButtonAction(_ sender: Any) {
234 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)
237 @objc func getDataForCurrentSearchQuery(){
238 if currentSearchQuery.count > 0 {
239 dataLoader.currentParams.search = currentSearchQuery
242 dataLoader.currentParams.search = nil
244 dataLoader.dataSource = [BookModel]()
245 tableView.reloadData()
246 loadData(more: false, fromRefreshControl: false)
250 extension SearchViewController: UITableViewDataSource{
251 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
252 return dataLoader.dataSource.count
255 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
257 let cell = tableView.dequeueReusableCell(withIdentifier: "BookTableViewCell", for: indexPath) as! BookTableViewCell
258 cell.setup(bookModel: dataLoader.dataSource[indexPath.row])
262 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
263 if indexPath.row > 0 && indexPath.row == (dataLoader.dataSource.count-1) && dataLoader.canLoadMore && !dataLoader.loadingMore{
264 let _ = loadData(more: true, fromRefreshControl: false)
269 extension SearchViewController: UITableViewDelegate{
270 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
271 tableView.deselectRow(at: indexPath, animated: true)
272 searchBar.resignFirstResponder()
273 if dataLoader.dataSource.count > indexPath.row{
274 navigationController?.pushViewController(BookDetailsViewController.instance(bookSlug: dataLoader.dataSource[indexPath.row].slug) , animated: true)
279 extension SearchViewController: FilterViewControllerDelegate{
280 func filterViewControllerDidSelectItems(kindsArray: [CategoryModel], epochsArray: [CategoryModel], genresArray: [CategoryModel], onlyLectures: Bool, hasAudiobook: Bool, filterChanged: Bool) {
283 filtersManager.selectedKindsArray = kindsArray
284 filtersManager.selectedEpochsArray = epochsArray
285 filtersManager.selectedGenresArray = genresArray
286 filtersManager.onlyLecturesCategory.checked = onlyLectures
287 filtersManager.hasAudiobookCategory.checked = hasAudiobook
289 filtersChanged(reloadData: true)
291 self.navigationController?.presentedViewController?.dismiss(animated: true, completion: nil)
295 extension SearchViewController: UISearchBarDelegate{
297 func textFieldShouldReturn(_ textField: UITextField) -> Bool {// called when 'return' key pressed. return NO to ignore.
298 textField.resignFirstResponder()
302 func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
303 var searchQueryCandidate = ""
304 if let txt = searchBar.text{
305 searchQueryCandidate = txt.trimmed
308 if(currentSearchQuery != searchQueryCandidate){
309 currentSearchQuery = searchQueryCandidate
310 getDataForCurrentSearchQuery()
314 func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
315 searchBar.resignFirstResponder()
318 func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
319 currentSearchQuery = searchText.trimmed
321 NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(SearchViewController.getDataForCurrentSearchQuery), object: nil)
322 self.perform(#selector(SearchViewController.getDataForCurrentSearchQuery), with: nil, afterDelay: 0.8)
326 extension SearchViewController: SearchFiltersManagerDelegate{
327 func searchFiltersManagerFiltersChanged(reloadData: Bool) {
328 filtersChanged(reloadData: reloadData)
332 extension SearchViewController: DataLoaderDelegate{
333 func dataLoaderFetchingServerFinished() {
334 footerViewActivityIndicator.stopAnimating()
337 func dataLoaderFetchingServerSuccess() {
338 noDataLabel.isHidden = dataLoader.dataSource.count > 0
339 tableView.reloadData()
342 func dataLoaderFetchingServerFailed(showRefreshButton: Bool) {
344 if showRefreshButton{
345 refreshDataButton.setIndicatorButtonState(state: .button)
347 view.makeToast("book_loading_error".localized, duration: 3.0, position: .bottom)