FacilitiesListViewController.swift 15.1 KB
Newer Older
1
2
3
4
5
//
//  LocationsListViewController.swift
//  WhatsOpen
//
//  Created by Zach Knox on 4/5/17.
Zach Knox's avatar
Zach Knox committed
6
//  Copyright © 2017 SRCT. Some rights reserved.
7
8
9
//

import UIKit
Zach Knox's avatar
Zach Knox committed
10
import DeckTransition
Zach Knox's avatar
Zach Knox committed
11
import RealmSwift
12

13
class FacilitiesListViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIViewControllerPreviewingDelegate {
14

15
    // array of all facilities
Zach Knox's avatar
Zach Knox committed
16
	var facilitiesArray = List<Facility>()
17
18
    
    // array of facilities that pass the current filters
Zac Wood's avatar
Zac Wood committed
19
20
    var filteredFacilities = List<Facility>()
    
21
    // passing in nil sets the search controller to be this controller
Zac Wood's avatar
Zac Wood committed
22
23
    let searchController = UISearchController(searchResultsController: nil)

Zach Knox's avatar
Zach Knox committed
24
	var filters = Filters()
25
	
26
27
28
29
	override var preferredStatusBarStyle: UIStatusBarStyle {
		return .default
	}
	
30
	@IBOutlet var LeftButton: UIBarButtonItem!
Zach Knox's avatar
Zach Knox committed
31

32
33
34
35
	@IBAction func RightButton(_ sender: Any) {
	}
	@IBOutlet var RightButton: UIBarButtonItem!
	
36
37
	@IBOutlet var settingsButton: UIBarButtonItem!
	
38
	@IBOutlet var LocationsList: UICollectionView!
Zach Knox's avatar
Zach Knox committed
39

40
	@IBOutlet var LocationsListLayout: UICollectionViewFlowLayout!
Zach Knox's avatar
Zach Knox committed
41

42
	@IBOutlet var favoritesControl: UISegmentedControl!
Zach Knox's avatar
Zach Knox committed
43
44
	var showFavorites = false

45
	@IBOutlet var LastUpdatedLabel: UIBarButtonItem!
46
47
    
    let refreshControl = UIRefreshControl()
Zach Knox's avatar
Zach Knox committed
48

Zach Knox's avatar
Zach Knox committed
49
	@IBAction func favoritesControlChanges(_ sender: Any) {
50
		switch self.favoritesControl.selectedSegmentIndex
Zach Knox's avatar
Zach Knox committed
51
52
53
		{
		case 0:
			showFavorites = false
Zach Knox's avatar
Zach Knox committed
54
			filteredFacilities = facilitiesArray
Zach Knox's avatar
Zach Knox committed
55
		case 1:
56
57
            showFavorites = true
			filteredFacilities = filterFacilitiesForFavorites()
Zach Knox's avatar
Zach Knox committed
58
59
60
61
62
		default:
			showFavorites = false
		}
		self.LocationsList.reloadData()
	}
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    
    /**
     Get all of the facilities that are favorited.
     
     - returns:
        List of facilities that are favorited
     */
    func filterFacilitiesForFavorites() -> List<Facility> {
        var favoriteFacilites = List<Facility>()
        
        // add the facility to favorites list if it is a favorite
        favoriteFacilites = facilitiesArray.filter({ (facility: Facility) -> Bool in
            return Utilities.isFavoriteFacility(facility)
        })
        
        return favoriteFacilites
    }
Zach Knox's avatar
Zach Knox committed
80

81
	override func viewWillLayoutSubviews() {
Zach Knox's avatar
Zach Knox committed
82
83
84
85
86
		LocationsListLayout.itemSize.width = getCellWidth()
		LocationsListLayout.invalidateLayout()
	}
	
	func getCellWidth() -> CGFloat {
87
88
		let windowWidth = self.view.frame.size.width
		
Zach Knox's avatar
Zach Knox committed
89
90
		if(windowWidth < 640) {
			return windowWidth - 20
91
92
		}
		else if(windowWidth >= 640 && windowWidth < 1024) {
Zach Knox's avatar
Zach Knox committed
93
			return (windowWidth / 2) - 15
94
95
		}
		else if(windowWidth >= 1024) {
Zach Knox's avatar
Zach Knox committed
96
			return (windowWidth / 3) - 15
97
98
		}
		
Zach Knox's avatar
Zach Knox committed
99
		return 0
100
	}
Zach Knox's avatar
Zach Knox committed
101

102
103
104
	@IBAction func RefreshButton(_ sender: Any) {
		refresh(sender)
	}
Zach Knox's avatar
Zach Knox committed
105

106
107
108
109
	override func viewWillAppear(_ animated: Bool) {
		LastUpdatedLabel.isEnabled = false
	}
	
Zach Knox's avatar
Zach Knox committed
110
	@objc func tapRecognizer(_ sender: UITapGestureRecognizer) {
Zach Knox's avatar
Zach Knox committed
111
112
113
		
		let tapLocation = sender.location(in: LocationsList)
		let indexPath = LocationsList.indexPathForItem(at: tapLocation)
Zac Wood's avatar
Zac Wood committed
114
        
Zach Knox's avatar
Zach Knox committed
115
		if((indexPath) != nil) {
Zac Wood's avatar
Zac Wood committed
116
117
118
119
            let destination = self.storyboard?.instantiateViewController(withIdentifier: "detailView") as? FacilityDetailViewController
            let tapped = self.LocationsList.cellForItem(at: indexPath!) as! FacilityCollectionViewCell
            destination!.facility = tapped.facility
            self.presentDetailView(destination!)
Zach Knox's avatar
Zach Knox committed
120
121
122
123
124
125
126
		}
	}
	
	func presentDetailView(_ destination: FacilityDetailViewController) {
		if(self.view.traitCollection.horizontalSizeClass == .regular && self.view.traitCollection.verticalSizeClass == .regular) {
			//do a popover here for the iPad
			//iPads are cool right?
Zach Knox's avatar
Zach Knox committed
127
128
129
130
			destination.modalPresentationStyle = .popover
			let popoverController = destination.popoverPresentationController
			popoverController?.permittedArrowDirections = .any
			popoverController?.sourceView = destination.view
Zac Wood's avatar
Zac Wood committed
131
132
133
134
135
136
137
138
            
            // present the detail view over the search controller if we're searching
            if searchController.isActive {
                searchController.present(destination, animated: true, completion: nil)
            }
            else {
                present(destination, animated: true, completion: nil)
            }
Zach Knox's avatar
Zach Knox committed
139
140
141
142
143
		}
		else {
			let destDelegate = DeckTransitioningDelegate()
			destination.modalPresentationStyle = .custom
			destination.transitioningDelegate = destDelegate
Zac Wood's avatar
Zac Wood committed
144
145
146
147
148
149
150
151
            
            // present the detail view over the search controller if we're searching
            if searchController.isActive {
                searchController.present(destination, animated: true, completion: nil)
            }
            else {
                present(destination, animated: true, completion: nil)
            }
Zach Knox's avatar
Zach Knox committed
152
		}
Zac Wood's avatar
Zac Wood committed
153
154
        
        
Zach Knox's avatar
Zach Knox committed
155
	}
Zac Wood's avatar
Zac Wood committed
156
157
158
159
160
161
162
163
164
    
    func configureSearchController() {
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        
        // add it to the navigationItem
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = true
    }
Zach Knox's avatar
Zach Knox committed
165
166
	
	override func viewDidLoad() {
167
168
        super.viewDidLoad()
		
169
170
		self.definesPresentationContext = true
		
Zach Knox's avatar
Zach Knox committed
171
		if(traitCollection.forceTouchCapability == .available) {
172
173
			registerForPreviewing(with: self, sourceView: self.LocationsList!)
		}
Zach Knox's avatar
Zach Knox committed
174
175
        
        navigationItem.title = "What's Open"
176
		navigationController?.navigationBar.prefersLargeTitles = true
Zach Knox's avatar
Zach Knox committed
177
        
Zac Wood's avatar
Zac Wood committed
178
        configureSearchController()
Zach Knox's avatar
Zach Knox committed
179
		
180
		LocationsListLayout.invalidateLayout()
Zach Knox's avatar
Zach Knox committed
181
		
182
183
184
		settingsButton.accessibilityLabel = "Settings"
		LastUpdatedLabel.accessibilityHint = ""
		
185
		LocationsListLayout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10)
Zach Knox's avatar
Zach Knox committed
186

Zach Knox's avatar
Zach Knox committed
187
188
189
		refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
		LocationsList.addSubview(refreshControl)
		LocationsList.alwaysBounceVertical = true
Zach Knox's avatar
Zach Knox committed
190

191
		SRCTNetworkController.performDownload { (facilities) in
Zach Knox's avatar
Zach Knox committed
192
			self.facilitiesArray = List(facilities)
193
194
			DispatchQueue.main.async {
				self.LocationsList.reloadData()
195
196
				let date = Date()
				self.LastUpdatedLabel.title = "Updated: " + self.shortDateFormat(date)
197
198
			}
		}
199
200
		
	}
Zac Wood's avatar
Zac Wood committed
201
202
203
204
205
206
207
208
209
    
    func isSearchBarEmpty() -> Bool {
        return searchController.searchBar.text?.isEmpty ?? true
    }
    
    func isSearching() -> Bool {
        return searchController.isActive && !isSearchBarEmpty()
    }
    
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    /**
     Filters facilities based on the text inputted into the search controller.
     
     - parameters:
        - searchText: text used to filter the facilities.
     - returns:
        List of filtered facilities. Facilities whose names, buildings, or categories match the search text are included.
     */
    func filterFacilitiesForSearchText(_ searchText: String) -> List<Facility> {
        var filtered: List<Facility>
        
        if showFavorites {
            let favoriteFacilities = filterFacilitiesForFavorites()
            
            if searchText == "" { // if the search text is empty, just return the favorites.
                filtered = favoriteFacilities
            } else {
                filtered = favoriteFacilities.filter({(facility: Facility) -> Bool in
                    let hasName = facility.facilityName.lowercased().contains(searchText.lowercased())
                    let hasBuilding = facility.facilityLocation?.building.lowercased().contains(searchText.lowercased()) ?? false
                    let hasCategory = facility.category?.categoryName.lowercased().contains(searchText.lowercased()) ?? false
                    
                    return hasName || hasBuilding || hasCategory
                })
            }
            
        } else {
            filtered = facilitiesArray.filter({(facility: Facility) -> Bool in
                let hasName = facility.facilityName.lowercased().contains(searchText.lowercased())
                let hasBuilding = facility.facilityLocation?.building.lowercased().contains(searchText.lowercased()) ?? false
                let hasCategory = facility.category?.categoryName.lowercased().contains(searchText.lowercased()) ?? false
                
                return hasName || hasBuilding || hasCategory
            })
        }
        
Zac Wood's avatar
Zac Wood committed
246
        LocationsList.reloadData()
247
        return filtered
Zac Wood's avatar
Zac Wood committed
248
    }
Zach Knox's avatar
Zach Knox committed
249
	
Zach Knox's avatar
Zach Knox committed
250
	@objc func refresh(_ sender: Any) {
Zach Knox's avatar
Zach Knox committed
251
252
		refreshControl.beginRefreshing()
		LocationsList.reloadData()
253
254
		let date = Date()
		LastUpdatedLabel.title = "Updated: " + shortDateFormat(date)
Zach Knox's avatar
Zach Knox committed
255
256
		refreshControl.endRefreshing()
	}
Zac Wood's avatar
Zac Wood committed
257
    
258
259
260
261
262
263
264
265
266
	func shortDateFormat(_ date: Date) -> String {
		let dateFormatter = DateFormatter()
		dateFormatter.dateStyle = .short
		dateFormatter.timeStyle = .short

		// US English Locale (en_US)
		dateFormatter.locale = Locale(identifier: "en_US")
		return dateFormatter.string(from: date)
	}
Zach Knox's avatar
Zach Knox committed
267
	
268
269
270
271
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
Zach Knox's avatar
Zach Knox committed
272

273
	func numberOfSections(in collectionView: UICollectionView) -> Int {
Zach Knox's avatar
Zach Knox committed
274
		return 1
275
	}
Zach Knox's avatar
Zach Knox committed
276

277
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
Zach Knox's avatar
Zach Knox committed
278
        return isSearching() || showFavorites ? self.filteredFacilities.count : self.facilitiesArray.count
279
	}
Zach Knox's avatar
Zach Knox committed
280

281
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
282
		let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! FacilityCollectionViewCell
Zach Knox's avatar
Zach Knox committed
283
284
285
286
287
288
289
		/*
		let windowRect = self.view.window!.frame
		let windowWidth = windowRect.size.width
		if(windowWidth <= 320) {
			cell.frame.size.width = 280
		}
		*/
Zach Knox's avatar
Zach Knox committed
290
        //Get tap of the cell
291
		cell.tapRecognizer.addTarget(self, action: #selector(FacilitiesListViewController.tapRecognizer(_:)))
Zach Knox's avatar
Zach Knox committed
292
        cell.gestureRecognizers = []
293
		cell.gestureRecognizers?.append(cell.tapRecognizer)
Zach Knox's avatar
Zach Knox committed
294
        
Zac Wood's avatar
Zac Wood committed
295
296
297
298
299
        
        let facility: Facility
        let dataArray: [Facility]
        
        // if something has been searched for, we want to use the filtered array as the data source
Zach Knox's avatar
Zach Knox committed
300
        if isSearching() || showFavorites {
Zac Wood's avatar
Zac Wood committed
301
302
303
304
305
306
307
            dataArray = placeOpenFacilitiesFirstInArray(filteredFacilities)
        } else {
            dataArray = placeOpenFacilitiesFirstInArray(facilitiesArray)
        }
        
		facility = dataArray[indexPath.row]
        
308
		cell.facility = facility
Zach Knox's avatar
Zach Knox committed
309
310
        
        //set labels
Zach Knox's avatar
Zach Knox committed
311
		cell.nameLabel.text = facility.facilityName
312
        cell.categoryLabel.text = facility.category?.categoryName.uppercased()
Zach Knox's avatar
Zach Knox committed
313

Zach Knox's avatar
Zach Knox committed
314
315
316
317
318
319
        cell.openClosedLabel.text = Utilities.openOrClosedUntil(facility)
        
        cell.timeDescriptionLabel.text = facility.facilityLocation?.building

        //change appearence based on open state
        let open = Utilities.isOpen(facility: facility)
320
		if(open == true) {
321
			//cell.openClosedLabel.text = "Open"
322
323
324
			cell.openClosedLabel.textColor = UIColor.black
			cell.openClosedLabel.backgroundColor = UIColor.white
			//cell.openClosedLabel.backgroundColor = UIColor(red:0.00, green:0.40, blue:0.20, alpha:1.0)
325
			cell.backgroundColor = UIColor(red:0.00, green:0.40, blue:0.20, alpha:1.0)
326
		} else {
327
			//cell.openClosedLabel.text = "Closed"
328
329
330
			cell.openClosedLabel.textColor = UIColor.white
			cell.openClosedLabel.backgroundColor = UIColor.black
			//cell.openClosedLabel.backgroundColor = UIColor.red
331
332
			cell.backgroundColor = UIColor.red

333
		}
Zach Knox's avatar
Zach Knox committed
334

Zach Knox's avatar
Zach Knox committed
335
        //Accessibility
336
        //TODO: FIX THIS
337
338
339
340
		cell.accessibilityLabel = cell.nameLabel.text! + ", Currently " + cell.openClosedLabel.text! + "." + cell.timeDescriptionLabel.text!
		cell.accessibilityHint = "Double Tap to view details"

		
341
342
343
		self.reloadInputViews()
		return cell
	}
Zach Knox's avatar
Zach Knox committed
344
345

	func getLocationArray(_ facilitiesArray: List<Facility>) -> [Facility] {
Zach Knox's avatar
Zach Knox committed
346
347
348
349
		if(!showFavorites) {
			return placeOpenFacilitiesFirstInArray(facilitiesArray)
		}
		else {
350
			return placeOpenFacilitiesFirstInArray(filteredFacilities)
Zach Knox's avatar
Zach Knox committed
351
		}
Zach Knox's avatar
Zach Knox committed
352
353


Zach Knox's avatar
Zach Knox committed
354
	}
Zach Knox's avatar
Zach Knox committed
355

Zac Wood's avatar
Zac Wood committed
356
357
358
	// Returns an array which has the open locations listed first
	// Could be improved in the future because currently this means you're checking
	// open status twice per cell
Zach Knox's avatar
Zach Knox committed
359
	func placeOpenFacilitiesFirstInArray(_ facilitiesArray: List<Facility>) -> [Facility] {
360
361
		var open = [Facility]()
		var closed = [Facility]()
Zach Knox's avatar
Zach Knox committed
362

363
364
365
366
367
368
369
370
371
372
373
		for i in facilitiesArray {
			if(Utilities.isOpen(facility: i)) {
				open.append(i)
			}
			else {
				closed.append(i)
			}
		}
		// Test
		return open + closed
	}
Zach Knox's avatar
Zach Knox committed
374

375
376
377
	func countForOpenAndClosedFacilities(_ facilitiesArray: Array<Facility>) -> (open: Int, closed: Int) {
		var open = 0
		var closed = 0
Zach Knox's avatar
Zach Knox committed
378

379
380
381
382
383
384
385
386
		for i in facilitiesArray {
			if(Utilities.isOpen(facility: i)) {
				open += 1
			}
			else {
				closed += 1
			}
		}
Zach Knox's avatar
Zach Knox committed
387

388
389
		return (open, closed)
	}
Zach Knox's avatar
Zach Knox committed
390
391


392
393
    // MARK: - Navigation

Zach Knox's avatar
Zach Knox committed
394
    //In a storyboard-based application, you will often want to do a little preparation before navigation
Zac Wood's avatar
Zac Wood committed
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
//    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//        // Get the new view controller using segue.destinationViewController.
//        if(segue.identifier == "toDetailView") {
//            let destination = segue.destination as! FacilityDetailViewController
//            let destDelegate = DeckTransitioningDelegate()
//            destination.transitioningDelegate = destDelegate
//            let tapped = sender as! FacilityCollectionViewCell //this is probably a bad idea just FYI future me
//            destination.facility = tapped.facility
//
//            // if we're in the search view, present on its controller
//            if searchController.isActive {
//                searchController.present(destination, animated: true, completion: nil)
//            } else {
//                present(destination, animated: true, completion: nil)
//            }
//        }
//        else if(segue.identifier == "toFilters") {
//            let destination = segue.destination as! UINavigationController
//            let filterView = destination.topViewController as! FiltersTableViewController
//            filterView.filters = self.filters
//        }
//
//        // Pass the selected object to the new view controller.
//    }
419
	
420
421
422
423
	// MARK: - Peek and Pop
	
	func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
		guard let indexPath = LocationsList?.indexPathForItem(at: location) else { return nil }
424
425
		let cell = LocationsList?.cellForItem(at: indexPath) as! FacilityCollectionViewCell
		guard let detailView = storyboard?.instantiateViewController(withIdentifier: "detailView") as? FacilityDetailViewController else { return nil }
426
427
		
		detailView.facility = cell.facility
Zach Knox's avatar
Zach Knox committed
428

429
430
431
432
		return detailView
	}
	
	func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
Zach Knox's avatar
Zach Knox committed
433
434
435
436
437
		let destDelegate = DeckTransitioningDelegate()
		viewControllerToCommit.modalPresentationStyle = .custom
		viewControllerToCommit.transitioningDelegate = destDelegate
		//If one day 3D touch comes to the iPad, this is no longer good.
		present(viewControllerToCommit, animated: true, completion: nil)
438
439
	}
	
440
}
Zac Wood's avatar
Zac Wood committed
441
442
443
444
445

// by implementing UISearchResultsUpdating, we can use this controller for the search controller
extension FacilitiesListViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        let searchText = searchController.searchBar.text ?? ""
446
        filteredFacilities = filterFacilitiesForSearchText(searchText)
Zac Wood's avatar
Zac Wood committed
447
448
449
    }
}