FacilitiesListViewController.swift 34.4 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
import StoreKit
13
import WhatsOpenKit
14

15
class FacilitiesListViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIViewControllerPreviewingDelegate, UICollectionViewDelegateFlowLayout {
16

17
	// Tell Realm to use this new configuration object for the default Realm
18
	let realm = try! Realm(configuration: WOPDatabaseController.getConfig())
Zach Knox's avatar
Zach Knox committed
19

20
21
	var facilitiesArray = List<WOPFacility>()
	var alertsList = List<WOPAlert>()
Zach Knox's avatar
Zach Knox committed
22
	
23
	var currentAlerts = List<WOPAlert>()
24
25
    
    // array of facilities that pass the current filters
26
    var filteredFacilities = List<WOPFacility>()
27
28
	
	// List which actually pertains to what is shown
29
	var shownFacilities = List<WOPFacility>()
Zac Wood's avatar
Zac Wood committed
30
    
31
    // passing in nil sets the search controller to be this controller
Zac Wood's avatar
Zac Wood committed
32
33
    let searchController = UISearchController(searchResultsController: nil)

34
	var filters = WOPFilters()
35
	
36
37
38
39
	override var preferredStatusBarStyle: UIStatusBarStyle {
		return .default
	}
	
40
41
	@IBOutlet var LeftButton: UIBarButtonItem!
	
42
43
	@IBOutlet var settingsButton: UIBarButtonItem!
	
44
	@IBOutlet var LocationsList: UICollectionView!
Zach Knox's avatar
Zach Knox committed
45

46
	@IBOutlet var LocationsListLayout: UICollectionViewFlowLayout!
47
	
48
	@IBOutlet var favoritesControl: UISegmentedControl!
Zach Knox's avatar
Zach Knox committed
49
	var showFavorites = false
50
51
    
    let refreshControl = UIRefreshControl()
Zach Knox's avatar
Zach Knox committed
52

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

86
	override func viewWillLayoutSubviews() {
87
		//LocationsListLayout.itemSize.width = getCellWidth()
Zach Knox's avatar
Zach Knox committed
88
		LocationsListLayout.invalidateLayout()
89
		LocationsList.reloadData()
Zach Knox's avatar
Zach Knox committed
90
91
	}
	
92
	/*
Zach Knox's avatar
Zach Knox committed
93
	func getCellWidth() -> CGFloat {
94
95
		let windowWidth = self.view.frame.size.width
		
Zach Knox's avatar
Zach Knox committed
96
97
		if(windowWidth < 640) {
			return windowWidth - 20
98
99
		}
		else if(windowWidth >= 640 && windowWidth < 1024) {
Zach Knox's avatar
Zach Knox committed
100
			return (windowWidth / 2) - 15
101
102
		}
		else if(windowWidth >= 1024) {
Zach Knox's avatar
Zach Knox committed
103
			return (windowWidth / 3) - 15
104
105
		}
		
Zach Knox's avatar
Zach Knox committed
106
		return 0
107
	}
108
	*/
Zach Knox's avatar
Zach Knox committed
109

110
	@IBAction func RefreshButton(_ sender: Any) {
Zach Knox's avatar
Zach Knox committed
111
		refresh(sender, forceUpdate: true)
112
		reloadWithFilters()
113
	}
114
115
	
	func checkFilterState() {
116
		if(filters.showOpen && filters.showClosed && filters.openFirst && filters.sortBy == WOPSortMethod.alphabetical) {
117
118
119
120
121
122
123
124
125
126
127
128
			for f in filters.onlyFromCategories {
				if(f.value != true) {
					LeftButton.title = "Filter (On)"
					return
				}
			}
			for f in filters.onlyFromLocations {
				if(f.value != true) {
					LeftButton.title = "Filter (On)"
					return
				}
			}
129
130
131
132
133
134
			for f in filters.onlyFromCampuses {
				if(f.value != true) {
					LeftButton.title = "Filter (On)"
					return
				}
			}
135
136
137
138
139
			LeftButton.title = "Filter"
			return
		}
		LeftButton.title = "Filter (On)"
	}
Zach Knox's avatar
Zach Knox committed
140

141
	override func viewWillAppear(_ animated: Bool) {
142
		checkFilterState()
143
		reloadWithFilters()
144
145
	}
	
Zach Knox's avatar
Zach Knox committed
146
	@objc func tapRecognizer(_ sender: UITapGestureRecognizer) {
Zach Knox's avatar
Zach Knox committed
147
148
149
		
		let tapLocation = sender.location(in: LocationsList)
		let indexPath = LocationsList.indexPathForItem(at: tapLocation)
150
151
152
		
		if(indexPath != nil) {
			if(indexPath?.section == 1 || currentAlerts.count == 0) {
153
				let destination =  self.storyboard?.instantiateViewController(withIdentifier: "detailView") as? FacilityDetailViewController
154
				let tapped = self.LocationsList.cellForItem(at: indexPath!) as! FacilityCollectionViewCell
155
156
				destination?.facility = tapped.facility
				self.presentDetailView(destination!, tapped: tapped)
157
158
			}
			else {
159
				let destination = self.storyboard?.instantiateViewController(withIdentifier: "alertDetail") as? AlertDetailViewController
160
				let tapped = self.LocationsList.cellForItem(at: indexPath!) as! AlertCollectionViewCell
161
162
				destination?.alert = tapped.alert
				self.presentDetailView(destination!, tapped: tapped)
163
			}
Zach Knox's avatar
Zach Knox committed
164
		}
165

Zach Knox's avatar
Zach Knox committed
166
167
	}
	
168
	var goodToGo = false
Zach Knox's avatar
Zach Knox committed
169
	@objc func toDetailFromSearch(_ notification: Notification) {
170
171
172
173
174
175
		func toDetailCompletion() {
			let dest = self.storyboard?.instantiateViewController(withIdentifier: "detailView") as! FacilityDetailViewController
			let userActivity = notification.object as? NSUserActivity
			if(userActivity == nil) {
				return // don't do anything
			}
176
			let facility = realm.objects(WOPFacilitiesModel.self)[0].facilities.filter(NSPredicate(format: "facilityName = '" + (userActivity?.title)! + "'")).first
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
			if(facility == nil) {
				return // don't do anything
			}
			dest.facility = facility!
			
			let detailViewWithButtons = self.storyboard?.instantiateViewController(withIdentifier: "detailViewButtons") as? DetailViewButtonsViewController
			detailViewWithButtons?.detailViewController = dest
			detailViewWithButtons?.facility = dest.facility
			let buttonDest = detailViewWithButtons!
			
			let finalDestination = self.storyboard?.instantiateViewController(withIdentifier: "pulling") as? PullingViewController // Fox only, no items
			finalDestination?.currentViewController = buttonDest
			let destDelegate = DeckTransitioningDelegate(isSwipeToDismissEnabled: true, dismissCompletion: begForReviews)
			finalDestination?.modalPresentationStyle = .custom
			finalDestination?.transitioningDelegate = destDelegate
			
			// present the detail view over the search controller if we're searching
			if searchController.isActive {
				searchController.present(finalDestination!, animated: true, completion: nil)
			}
			else {
				present(finalDestination!, animated: true, completion: nil)
			}
200
		}
Zach Knox's avatar
Zach Knox committed
201
		
202
203
204
205
206
		if(goodToGo) {
			toDetailCompletion()
		} else {
			sleep(1)
			update(notification, completion: toDetailCompletion)
Zach Knox's avatar
Zach Knox committed
207
208
		}
	}
Zach Knox's avatar
Zach Knox committed
209
210
211
212

	@objc func toDetailFromURL(_ notification: Notification) {
		let facilityEncoded = notification.userInfo!["facility"] as? String
		let facilityDecoded = facilityEncoded?.removingPercentEncoding
213
		let facility = realm.objects(WOPFacilitiesModel.self)[0].facilities.filter(NSPredicate(format: "facilityName = \"" + (facilityDecoded)! + "\"")).first
Zach Knox's avatar
Zach Knox committed
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
		if(facility == nil) {
			return // don't do anything
		}
		
		let dest = self.storyboard?.instantiateViewController(withIdentifier: "detailView") as! FacilityDetailViewController
		dest.facility = facility!
		
		let detailViewWithButtons = self.storyboard?.instantiateViewController(withIdentifier: "detailViewButtons") as? DetailViewButtonsViewController
		detailViewWithButtons?.detailViewController = dest
		detailViewWithButtons?.facility = dest.facility
		let buttonDest = detailViewWithButtons!
		
		let finalDestination = self.storyboard?.instantiateViewController(withIdentifier: "pulling") as? PullingViewController // Fox only, no items
		finalDestination?.currentViewController = buttonDest
		let destDelegate = DeckTransitioningDelegate(isSwipeToDismissEnabled: true, dismissCompletion: begForReviews)
		finalDestination?.modalPresentationStyle = .custom
		finalDestination?.transitioningDelegate = destDelegate
		
		// present the detail view over the search controller if we're searching
		if searchController.isActive {
			searchController.present(finalDestination!, animated: true, completion: nil)
		}
		else {
			present(finalDestination!, animated: true, completion: nil)
		}
	}

241
	func presentDetailView(_ destination: UIViewController, tapped: UICollectionViewCell) {
242
243
244
245
246
247
248
249
250
251
		var trueDest: UIViewController
		if destination is FacilityDetailViewController {
			let detailViewWithButtons = self.storyboard?.instantiateViewController(withIdentifier: "detailViewButtons") as? DetailViewButtonsViewController
			detailViewWithButtons?.detailViewController = (destination as! FacilityDetailViewController)
			detailViewWithButtons?.facility = (destination as! FacilityDetailViewController).facility
			trueDest = detailViewWithButtons!
		}
		else {
			trueDest = destination
		}
Zach Knox's avatar
Zach Knox committed
252
253
254
		if(self.view.traitCollection.horizontalSizeClass == .regular && self.view.traitCollection.verticalSizeClass == .regular) {
			//do a popover here for the iPad
			//iPads are cool right?
255
256
			trueDest.modalPresentationStyle = .popover
			let popoverController = trueDest.popoverPresentationController
Zach Knox's avatar
Zach Knox committed
257
			popoverController?.permittedArrowDirections = .any
258
259
			popoverController?.sourceView = tapped.contentView
			popoverController?.sourceRect = tapped.bounds
Zac Wood's avatar
Zac Wood committed
260
261
262
            
            // present the detail view over the search controller if we're searching
            if searchController.isActive {
263
                searchController.present(trueDest, animated: true, completion: nil)
Zac Wood's avatar
Zac Wood committed
264
265
            }
            else {
266
                present(trueDest, animated: true, completion: nil)
Zac Wood's avatar
Zac Wood committed
267
            }
Zach Knox's avatar
Zach Knox committed
268
269
		}
		else {
270
			let finalDestination = self.storyboard?.instantiateViewController(withIdentifier: "pulling") as? PullingViewController // Fox only, no items
271
			finalDestination?.currentViewController = trueDest
272
			let destDelegate = DeckTransitioningDelegate(isSwipeToDismissEnabled: true, dismissCompletion: begForReviews)
273
274
			finalDestination?.modalPresentationStyle = .custom
			finalDestination?.transitioningDelegate = destDelegate
Zac Wood's avatar
Zac Wood committed
275
276
277
            
            // present the detail view over the search controller if we're searching
            if searchController.isActive {
278
				searchController.present(finalDestination!, animated: true, completion: nil)
Zac Wood's avatar
Zac Wood committed
279
280
            }
            else {
281
				present(finalDestination!, animated: true, completion: nil)
Zac Wood's avatar
Zac Wood committed
282
            }
Zach Knox's avatar
Zach Knox committed
283
		}
Zac Wood's avatar
Zac Wood committed
284
285
        
        
Zach Knox's avatar
Zach Knox committed
286
	}
287
288
289
	
	func begForReviews(_ dismissed: Bool) {
		// MARK - Begging for App Reviews
Zach Knox's avatar
Zach Knox committed
290
291
		let defaults = WOPDatabaseController.getDefaults()
		let prompt = defaults.integer(forKey: "reviewPrompt")
292
293
		if(arc4random_uniform(100) > 92 && prompt >= 4) {
			SKStoreReviewController.requestReview()
Zach Knox's avatar
Zach Knox committed
294
			defaults.set(0, forKey: "reviewPrompt")
295
296
		}
		else {
Zach Knox's avatar
Zach Knox committed
297
			defaults.set(prompt + 1, forKey: "reviewPrompt")
298
299
		}
	}
Zac Wood's avatar
Zac Wood committed
300
301
302
303
304
305
306
307
308
    
    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
309
310
	
	override func viewDidLoad() {
Zach Knox's avatar
Zach Knox committed
311
		NotificationCenter.default.addObserver(self, selector: #selector(toDetailFromSearch(_:)), name: NSNotification.Name(rawValue: "launchToFacility"), object: nil)
Zach Knox's avatar
Zach Knox committed
312
		NotificationCenter.default.addObserver(self, selector: #selector(toDetailFromURL(_:)), name: NSNotification.Name(rawValue: "openFacilityFromURL"), object: nil)
Zach Knox's avatar
Zach Knox committed
313
		
314
        super.viewDidLoad()
Zach Knox's avatar
Zach Knox committed
315
		let nc = NotificationCenter.default
Zach Knox's avatar
Zach Knox committed
316
		nc.addObserver(self, selector: #selector(anyRefresh(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
317
		
318
		self.definesPresentationContext = true
Zach Knox's avatar
Zach Knox committed
319
320
321
322
323
		
		if(traitCollection.forceTouchCapability == .available) {
			registerForPreviewing(with: self, sourceView: self.LocationsList!)
		}
		
Zach Knox's avatar
Zach Knox committed
324
        navigationItem.title = "What's Open"
325
		navigationController?.navigationBar.prefersLargeTitles = true
326
327
		navigationItem.largeTitleDisplayMode = .always
		
Zac Wood's avatar
Zac Wood committed
328
        configureSearchController()
Zach Knox's avatar
Zach Knox committed
329
		
330
		LocationsListLayout.invalidateLayout()
Zach Knox's avatar
Zach Knox committed
331
		
332
333
		settingsButton.accessibilityLabel = "Settings"
		
Zach Knox's avatar
Zach Knox committed
334
		LocationsListLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
Zach Knox's avatar
Zach Knox committed
335

Zach Knox's avatar
Zach Knox committed
336
		refreshControl.addTarget(self, action: #selector(forceRefresh(_:)), for: .valueChanged)
337
		LocationsList.refreshControl = refreshControl
Zach Knox's avatar
Zach Knox committed
338
		LocationsList.alwaysBounceVertical = true
Zach Knox's avatar
Zach Knox committed
339

340
		refreshControl.beginRefreshing()
Zach Knox's avatar
Zach Knox committed
341
		refresh(self, forceUpdate: false)
Zach Knox's avatar
Zach Knox committed
342
		
343
		reloadWithFilters()
Zach Knox's avatar
Zach Knox committed
344
		
345
	}
Zach Knox's avatar
Zach Knox committed
346
	
347
	func reloadWithFilters() {
Zach Knox's avatar
Zach Knox committed
348
349
		
		// Facilities
350
		filteredFacilities = filters.applyFiltersOnFacilities(facilitiesArray)
351
352

		
Zach Knox's avatar
Zach Knox committed
353
		let defaults = WOPDatabaseController.getDefaults()
354
355
356
357
358
359
360

		
		// Campuses
		// By the time you've called reloadWithFilters(), the defaults list should already be updated to include
		// all campuses via updateFilterLists
		let campusFilters = defaults.dictionary(forKey: "campuses") as! [String: Bool]?
		
361
		let filteredByCampus = List<WOPFacility>()
362
363
364
365
366
367
368
		for facility in filteredFacilities {
			if campusFilters![(facility.facilityLocation?.campus.lowercased())!]! {
				filteredByCampus.append(facility)
			}
		}
		filteredFacilities = filteredByCampus
		
369
370
		shownFacilities = filteredFacilities
		favoritesControlChanges(self)
Zach Knox's avatar
Zach Knox committed
371
372
		
		// Alerts
373
		let shown = List<WOPAlert>()
Zach Knox's avatar
Zach Knox committed
374
375
376
		let formatter = ISO8601DateFormatter()
		formatter.timeZone = TimeZone(identifier: "America/New_York")
		let now = Date()
377
		let alertFilers = defaults.dictionary(forKey: "alerts") as! [String: Bool]?
Zach Knox's avatar
Zach Knox committed
378
379
		for alert in alertsList {
			if now.isGreaterThanDate(dateToCompare: formatter.date(from: alert.startDate)!)  && now.isLessThanDate(dateToCompare: formatter.date(from: alert.endDate)!) {
Zach Knox's avatar
Zach Knox committed
380
381
				switch alert.urgency {
				case "info":
Zach Knox's avatar
Zach Knox committed
382
					if(alertFilers!["informational"])! {
Zach Knox's avatar
Zach Knox committed
383
384
385
						shown.append(alert)
					}
				case "minor":
Zach Knox's avatar
Zach Knox committed
386
					if(alertFilers!["minor alerts"])! {
Zach Knox's avatar
Zach Knox committed
387
388
389
						shown.append(alert)
					}
				case "major":
Zach Knox's avatar
Zach Knox committed
390
					if(alertFilers!["major alerts"])! {
Zach Knox's avatar
Zach Knox committed
391
392
393
394
395
						shown.append(alert)
					}
				default:
					shown.append(alert)
				}
Zach Knox's avatar
Zach Knox committed
396
397
398
399
400
			}
		}
		currentAlerts = shown
		
		
401
402
		LocationsList.reloadData()
	}
Zac Wood's avatar
Zac Wood committed
403
404
405
406
407
408
409
410
411
    
    func isSearchBarEmpty() -> Bool {
        return searchController.searchBar.text?.isEmpty ?? true
    }
    
    func isSearching() -> Bool {
        return searchController.isActive && !isSearchBarEmpty()
    }
    
412
413
414
415
416
417
418
419
    /**
     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.
     */
420
421
    func filterFacilitiesForSearchText(_ searchText: String) -> List<WOPFacility> {
        var filtered: List<WOPFacility>
422
423
		
		/*
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        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
                })
            }
438

439
        } else {
440
441
442
443
444
445
		  */
		if searchText == "" {
			filtered = shownFacilities
			LocationsList.reloadData()
			return shownFacilities
		}
446
		filtered = filteredFacilities.filter({(facility: WOPFacility) -> Bool in
447
448
			let hasName = facility.facilityName.lowercased().contains(searchText.lowercased())
			let hasBuilding = facility.facilityLocation?.building.lowercased().contains(searchText.lowercased()) ?? false
Jesse Scearce's avatar
Jesse Scearce committed
449
            let hasAbbreviation = facility.facilityLocation?.abbreviation.lowercased().contains(searchText.lowercased()) ?? false
450
			let hasCategory = facility.category?.categoryName.lowercased().contains(searchText.lowercased()) ?? false
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
			var hasTag = false
			for tag in facility.facilityTags! {
				if hasTag {
					break
				}
				if tag.tag.lowercased().contains(searchText.lowercased()) {
					hasTag = true
				}
			}
			var hasLabel = false
			for label in facility.labels! {
				if hasLabel {
					break
				}
				if label.tag.lowercased().contains(searchText.lowercased()) {
					hasLabel = true
				}
			}
Jesse Scearce's avatar
Jesse Scearce committed
469
			return hasName || hasBuilding || hasCategory || hasTag || hasAbbreviation
470
		})
471
        
Zac Wood's avatar
Zac Wood committed
472
        LocationsList.reloadData()
473
        return filtered
Zac Wood's avatar
Zac Wood committed
474
    }
Zach Knox's avatar
Zach Knox committed
475
	
Zach Knox's avatar
Zach Knox committed
476
477
478
479
480
481
482
483
	
	// These functions are for use by selectors
	@objc private func forceRefresh(_ sender: Any) {
		refresh(sender, forceUpdate: true)
	}
	@objc private func anyRefresh(_ sender: Any) {
		refresh(sender, forceUpdate: false)
	}
484
485
486
487
	/*
	* Reloads data, either calling update() to attempt a download
	* or simply pulling from the realm
	*/
488
	@objc func refresh(_ sender: Any, forceUpdate: Bool = true) {
Zach Knox's avatar
Zach Knox committed
489
		refreshControl.beginRefreshing()
Zach Knox's avatar
Zach Knox committed
490
491
492
493
		if(forceUpdate) {
			update(sender);
		}
		else {
494
			let results = realm.objects(WOPFacilitiesModel.self)
Zach Knox's avatar
Zach Knox committed
495
496
497
			if results.count > 0 {
				let model = results[0]
				let facilities = model.facilities
Zach Knox's avatar
Zach Knox committed
498
				let alerts = model.alerts
Zach Knox's avatar
Zach Knox committed
499
500
				let lastUpdated = model.lastUpdated
				
501
502
				if((facilities.isEmpty && alerts.isEmpty) || lastUpdated.isLessThanDate(dateToCompare:
				  	Date(timeIntervalSinceNow: -43200.0))) {
Zach Knox's avatar
Zach Knox committed
503
504
505
506
					update(sender)
				}
				else {
					facilitiesArray = facilities
Zach Knox's avatar
Zach Knox committed
507
					alertsList = alerts
508
					self.refreshControl.attributedTitle = NSAttributedString(string: "Last Updated: " + self.shortDateFormat(lastUpdated))
509
				  	goodToGo = true
Zach Knox's avatar
Zach Knox committed
510
511
512
513
514
515
516
				}
			}
			else {
				update(sender)
			}

		}
Zac Wood's avatar
Zac Wood committed
517
				
518
		updateFiltersLists()
519
		checkFilterState()
520
		reloadWithFilters()
Zac Wood's avatar
Zac Wood committed
521
        
522
523
524
525
		refreshControl.endRefreshing()
	}
	
	func updateFiltersLists() {
526
		// Add locations and categories to filters
Zach Knox's avatar
Zach Knox committed
527
		let defaults = WOPDatabaseController.getDefaults()
528
		var campusFilters = defaults.dictionary(forKey: "campuses") as! [String: Bool]?
529
530
		for f in facilitiesArray {
			if(!filters.onlyFromCategories.keys.contains((f.category?.categoryName)!)) {
531
				filters.onlyFromCategories.updateValue(true, forKey: (f.category?.categoryName)!.lowercased())
532
533
			}
			if(!filters.onlyFromLocations.keys.contains((f.facilityLocation?.building)!)) {
534
				filters.onlyFromLocations.updateValue(true, forKey: (f.facilityLocation?.building)!.lowercased())
535
			}
536
537
			if(!campusFilters!.keys.contains((f.facilityLocation?.campus)!)) {
				campusFilters!.updateValue(true, forKey: (f.facilityLocation?.campus)!.lowercased())
538
			}
539
		}
540
		defaults.set(campusFilters, forKey: "campuses")
Zach Knox's avatar
Zach Knox committed
541
	}
Zach Knox's avatar
Zach Knox committed
542
	
543
544
545
546
	/*
	* Attempts to update facilitiesArray from the network
	* and place that new information into Realm
	*/
547
	func update(_ sender: Any, completion: (() -> ())? = nil) {
548
		WOPDownloadController.performDownload { (facilities) in
Zach Knox's avatar
Zach Knox committed
549
550
			if(facilities == nil) {
				DispatchQueue.main.async {
551
					let results = self.realm.objects(WOPFacilitiesModel.self)
Zach Knox's avatar
Zach Knox committed
552
553
554
555
556
557
					if results.count > 0 {
						let model = results[0]
						let facilitiesFromDB = model.facilities
						let lastUpdated = model.lastUpdated
						
						self.facilitiesArray = facilitiesFromDB
558
						self.updateFiltersLists()
559
						self.reloadWithFilters()
560
561
						self.refreshControl.attributedTitle = NSAttributedString(string: "Last Updated: " + self.shortDateFormat(lastUpdated))
						self.refreshControl.endRefreshing()
562
563
564
565
						self.goodToGo = true
						if(completion != nil) {
							completion!()
						}
Zach Knox's avatar
Zach Knox committed
566
567
					}
					else {
568
						self.facilitiesArray = List<WOPFacility>()
Zach Knox's avatar
Zach Knox committed
569
570
					}
				}
Zach Knox's avatar
Zach Knox committed
571
572
573
574
575
576
			}
			else {
				self.facilitiesArray = facilities!
				
				DispatchQueue.main.async {
					let date = Date()
577
					self.refreshControl.attributedTitle = NSAttributedString(string: "Last Updated: " + self.shortDateFormat(date))
Zach Knox's avatar
Zach Knox committed
578

579
					let results = self.realm.objects(WOPFacilitiesModel.self)
Zach Knox's avatar
Zach Knox committed
580
					if results.count == 0 {
581
						let model = WOPFacilitiesModel()
Zach Knox's avatar
Zach Knox committed
582
583
584
585
						for f in facilities! {
							model.facilities.append(f)
						}
						model.lastUpdated = date
Zach Knox's avatar
Zach Knox committed
586
587
588
						try! self.realm.write {
							self.realm.add(model)
						}
589
590
591
592
						self.goodToGo = true
						if(completion != nil) {
							completion!()
						}
Zach Knox's avatar
Zach Knox committed
593
594
595
596
					}
					else {
						let fromRealm = results[0]
						try! self.realm.write {
Zach Knox's avatar
Zach Knox committed
597
598
599
600
601
							fromRealm.facilities.removeAll()
							for f in facilities! {
								fromRealm.facilities.append(f)
							}
							fromRealm.lastUpdated = date
Zach Knox's avatar
Zach Knox committed
602
						}
Zach Knox's avatar
Zach Knox committed
603
					}
604
605
					self.updateFiltersLists()
					self.reloadWithFilters()
606
					self.refreshControl.endRefreshing()
607
608
609
610
					self.goodToGo = true
					if(completion != nil) {
						completion!()
					}
Zach Knox's avatar
Zach Knox committed
611
				}
Zach Knox's avatar
Zach Knox committed
612
			}
Zach Knox's avatar
Zach Knox committed
613
		}
614
		WOPDownloadController.performAlertsDownload { (alerts) in
Zach Knox's avatar
Zach Knox committed
615
616
			if(alerts == nil) {
				DispatchQueue.main.async {
617
					let results = self.realm.objects(WOPFacilitiesModel.self)
Zach Knox's avatar
Zach Knox committed
618
619
					if results.count > 0 {
						let model = results[0]
Zach Knox's avatar
Zach Knox committed
620
						self.alertsList = model.alerts
Zach Knox's avatar
Zach Knox committed
621
622
					}
					else {
623
						self.alertsList = List<WOPAlert>()
Zach Knox's avatar
Zach Knox committed
624
625
626
627
628
629
630
					}
				}
			}
			else {
				self.alertsList = alerts!
				
				DispatchQueue.main.async {
631
					let results = self.realm.objects(WOPFacilitiesModel.self)
Zach Knox's avatar
Zach Knox committed
632
633
					if results.count == 0 {
						try! self.realm.write {
634
							let model = WOPFacilitiesModel()
Zach Knox's avatar
Zach Knox committed
635
636
637
							for a in alerts! {
								model.alerts.append(a)
							}
Zach Knox's avatar
Zach Knox committed
638
639
640
641
642
643
							self.realm.add(model)
						}
					}
					else {
						let fromRealm = results[0]
						try! self.realm.write {
Zach Knox's avatar
Zach Knox committed
644
645
646
647
							fromRealm.alerts.removeAll()
							for a in alerts! {
								fromRealm.alerts.append(a)
							}
Zach Knox's avatar
Zach Knox committed
648
649
						}
					}
650
					self.reloadWithFilters()
Zach Knox's avatar
Zach Knox committed
651
652
				}
			}
653
		}
Zach Knox's avatar
Zach Knox committed
654
	}
Zac Wood's avatar
Zac Wood committed
655
    
656
657
658
659
660
661
662
663
664
	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
665
	
666
667
668
669
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
Zach Knox's avatar
Zach Knox committed
670

671
	func numberOfSections(in collectionView: UICollectionView) -> Int {
Zach Knox's avatar
Zach Knox committed
672
		if currentAlerts.count > 0 {
Zach Knox's avatar
Zach Knox committed
673
674
675
676
677
			return 2
		}
		else {
			return 1
		}
678
	}
Zach Knox's avatar
Zach Knox committed
679

680
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
Zach Knox's avatar
Zach Knox committed
681
		if(section == 1 || currentAlerts.count == 0) {
Zach Knox's avatar
Zach Knox committed
682
683
684
685
			return shownFacilities.count
		}
		else {
			// TODO: get current alerts, not just any alerts
Zach Knox's avatar
Zach Knox committed
686
			return currentAlerts.count
Zach Knox's avatar
Zach Knox committed
687
688
		}
		
689
	}
Zach Knox's avatar
Zach Knox committed
690

691
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
Zach Knox's avatar
Zach Knox committed
692
		
Zach Knox's avatar
Zach Knox committed
693
		if (indexPath.section == 1 || currentAlerts.count == 0) {
Zach Knox's avatar
Zach Knox committed
694
695
696
697
698
			let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! FacilityCollectionViewCell
			/*
			let windowRect = self.view.window!.frame
			let windowWidth = windowRect.size.width
			if(windowWidth <= 320) {
Zach Knox's avatar
Zach Knox committed
699
			cell.frame.size.width = 280
Zach Knox's avatar
Zach Knox committed
700
701
702
703
704
705
706
707
			}
			*/
			//Get tap of the cell
			cell.tapRecognizer.addTarget(self, action: #selector(FacilitiesListViewController.tapRecognizer(_:)))
			cell.gestureRecognizers = []
			cell.gestureRecognizers?.append(cell.tapRecognizer)
			
			
708
			let facility: WOPFacility
Zach Knox's avatar
Zach Knox committed
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
			//let dataArray: [Facility]
			
			/*
			// if something has been searched for, we want to use the filtered array as the data source
			if isSearching() || showFavorites {
			dataArray = placeOpenFacilitiesFirstInArray(filteredFacilities)
			} else {
			dataArray = placeOpenFacilitiesFirstInArray(facilitiesArray)
			}
			*/
			
			
			
			facility = shownFacilities[indexPath.row]
			
			cell.facility = facility
			
			//set labels
Zach Knox's avatar
Zach Knox committed
727
728
729
730
731
732
			var name = facility.facilityName
			let separator = name.index(of: "[")
			if separator != nil {
				name = String(name[..<separator!]).replacingOccurrences(of: "\\s+$",with: "", options: .regularExpression)
			}
			cell.nameLabel.text = name
Zach Knox's avatar
Zach Knox committed
733
734
			cell.categoryLabel.text = facility.category?.categoryName.uppercased()
			
735
			cell.openClosedLabel.text = WOPUtilities.openOrClosedUntil(facility)
Zach Knox's avatar
Zach Knox committed
736
			
Zach Knox's avatar
Zach Knox committed
737
			// TODO: Change the name of this label
Zach Knox's avatar
Zach Knox committed
738
739
740
			cell.timeDescriptionLabel.text = facility.facilityLocation?.building
			
			//change appearence based on open state
741
			let open = WOPUtilities.isOpen(facility: facility)
Zach Knox's avatar
Zach Knox committed
742
743
744
745
746
747
748
749
750
751
752
			if(open == true) {
				//cell.openClosedLabel.text = "Open"
				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)
				cell.backgroundColor = UIColor(red:0.00, green:0.40, blue:0.20, alpha:1.0)
			} else {
				//cell.openClosedLabel.text = "Closed"
				cell.openClosedLabel.textColor = UIColor.white
				cell.openClosedLabel.backgroundColor = UIColor.black
				//cell.openClosedLabel.backgroundColor = UIColor.red
Eyad Hasan's avatar
Eyad Hasan committed
753
				cell.backgroundColor = UIColor(red:0.17, green:0.17, blue: 0.17, alpha: 1.0)
Zach Knox's avatar
Zach Knox committed
754
755
756
757
				
			}
			
			//Accessibility
758
			cell.accessibilityLabel = cell.nameLabel.text! + " " + cell.categoryLabel.text! + ", Currently " + cell.openClosedLabel.text! + ". Located in" + cell.timeDescriptionLabel.text!
Zach Knox's avatar
Zach Knox committed
759
760
761
			cell.accessibilityHint = "Double Tap to view details"
			
			
762
763
764
765
766
767
768
			//Shadows
			cell.layer.shadowColor = UIColor.black.cgColor
			cell.layer.shadowOffset = CGSize(width: 0, height: 3)
			cell.layer.shadowRadius = 7.0
			cell.layer.shadowOpacity = 0.4
			cell.layer.masksToBounds = false
			cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: cell.layer.cornerRadius).cgPath
Zach Knox's avatar
Zach Knox committed
769
			self.reloadInputViews()
Zach Knox's avatar
Zach Knox committed
770

Zach Knox's avatar
Zach Knox committed
771
			return cell
Zach Knox's avatar
Zach Knox committed
772
		}
Zach Knox's avatar
Zach Knox committed
773
774
		else {
			// Do Alerts things here
Zach Knox's avatar
Zach Knox committed
775
			let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Alert Cell", for: indexPath) as! AlertCollectionViewCell
776
			cell.viewWidth = self.view.frame.width
777
778
779
780
			cell.alert = currentAlerts[indexPath.row]
			cell.tapRecognizer.addTarget(self, action: #selector(FacilitiesListViewController.tapRecognizer(_:)))
			cell.gestureRecognizers = []
			cell.gestureRecognizers?.append(cell.tapRecognizer)
Zach Knox's avatar
Zach Knox committed
781
			
Zach Knox's avatar
Zach Knox committed
782
783
784
			switch currentAlerts[indexPath.row].urgency {
			case "info":
				cell.imageView.image = #imageLiteral(resourceName: "info")
785
				cell.imageView.accessibilityLabel = "Info"
Zach Knox's avatar
Zach Knox committed
786
787
			case "minor":
				cell.imageView.image = #imageLiteral(resourceName: "minor")
788
				cell.imageView.accessibilityLabel = "Minor Alert"
Zach Knox's avatar
Zach Knox committed
789
790
			case "major":
				cell.imageView.image = #imageLiteral(resourceName: "major")
791
				cell.imageView.accessibilityLabel = "Major Alert"
Zach Knox's avatar
Zach Knox committed
792
793
			case "emergency":
				cell.imageView.image = #imageLiteral(resourceName: "emergency")
794
				cell.imageView.accessibilityLabel = "Emergency Alert"
Zach Knox's avatar
Zach Knox committed
795
796
			default:
				cell.imageView.image = #imageLiteral(resourceName: "major")
797
				cell.imageView.accessibilityLabel = "Alert"
Zach Knox's avatar
Zach Knox committed
798
			}
Zach Knox's avatar
Zach Knox committed
799
			cell.messageLabel.text = currentAlerts[indexPath.row].message
800

Zach Knox's avatar
Zach Knox committed
801
			return cell
802
		}
Zach Knox's avatar
Zach Knox committed
803

804
	}
805
	
806
	// MARK - Collection View Cell Layout
807
	func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
Zach Knox's avatar
Zach Knox committed
808
		if(indexPath.section == 1 || currentAlerts.count == 0) {
809
810
811
			let height = LocationsListLayout.itemSize.height
			let width: CGFloat
			
812
813
			let edgeInsets = self.view.safeAreaInsets
			let windowWidth = self.view.frame.size.width - edgeInsets.left - edgeInsets.right
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
			
			if(windowWidth < 640) {
				width = windowWidth - 20
			}
			else if(windowWidth >= 640 && windowWidth < 1024) {
				width = (windowWidth / 2) - 15
			}
			else if(windowWidth >= 1024) {
				width = (windowWidth / 3) - 15
			}
			else {
				width = windowWidth - 20
			}
			
			return CGSize(width: width, height: height)
		}
		else {
831
832
			let edgeInsets = self.view.safeAreaInsets
			return CGSize(width: self.view.frame.size.width - edgeInsets.left - edgeInsets.right, height: 43)
833
834
		}
	}
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
	
	func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
		if(section == 1 || currentAlerts.count == 0) {
			return 15
		}
		else {
			return 0
		}
	}
	
	func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
		if(section == 1 || currentAlerts.count == 0) {
			return 10
		}
		else {
			return 0
		}
	}
853
854
855

	func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
		var sectionInsets = LocationsListLayout.sectionInset
Zach Knox's avatar
Zach Knox committed
856
		if(section != 1 && currentAlerts.count != 0) {
857
858
			sectionInsets.top = 0
		}
Zach Knox's avatar
Zach Knox committed
859
860
861
		else if currentAlerts.count == 0 {
			sectionInsets.top = 15
		}
862
863
		return sectionInsets
	}
Zach Knox's avatar
Zach Knox committed
864

865
866
	
	//unused
867
	func getLocationArray(_ facilitiesArray: List<WOPFacility>) -> [WOPFacility] {
Zach Knox's avatar
Zach Knox committed
868
869
870
871
		if(!showFavorites) {
			return placeOpenFacilitiesFirstInArray(facilitiesArray)
		}
		else {
872
			return placeOpenFacilitiesFirstInArray(filteredFacilities)
Zach Knox's avatar
Zach Knox committed
873
		}
Zach Knox's avatar
Zach Knox committed
874
875


Zach Knox's avatar
Zach Knox committed
876
	}
877
	
878
	//unused
Zac Wood's avatar
Zac Wood committed
879
880
881
	// 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
882
883
884
	func placeOpenFacilitiesFirstInArray(_ facilitiesArray: List<WOPFacility>) -> [WOPFacility] {
		var open = [WOPFacility]()
		var closed = [WOPFacility]()
Zach Knox's avatar
Zach Knox committed
885

886
		for i in facilitiesArray {
887
			if(WOPUtilities.isOpen(facility: i)) {
888
889
890
891
892
893
894
895
896
				open.append(i)
			}
			else {
				closed.append(i)
			}
		}
		// Test
		return open + closed
	}
Zach Knox's avatar
Zach Knox committed
897

898
	//unused
899
	func countForOpenAndClosedFacilities(_ facilitiesArray: Array<WOPFacility>) -> (open: Int, closed: Int) {
900
901
		var open = 0
		var closed = 0
Zach Knox's avatar
Zach Knox committed
902

903
		for i in facilitiesArray {
904
			if(WOPUtilities.isOpen(facility: i)) {
905
906
907
908
909
910
				open += 1
			}
			else {
				closed += 1
			}
		}
Zach Knox's avatar
Zach Knox committed
911

912
913
		return (open, closed)
	}
Zach Knox's avatar
Zach Knox committed
914
915


916
917
    // MARK: - Navigation

Zach Knox's avatar
Zach Knox committed
918
	//unused
Zach Knox's avatar
Zach Knox committed
919
    //In a storyboard-based application, you will often want to do a little preparation before navigation
Zach Knox's avatar
Zach Knox committed
920
921
922
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        if(segue.identifier == "toDetailView") {
923
            let destination = segue.destination as! PullingViewController
Zach Knox's avatar
Zach Knox committed
924
			var destChild = destination.children[0] as! FacilityDetailViewController
925
			destChild = self.storyboard?.instantiateViewController(withIdentifier: "detailView") as! FacilityDetailViewController
Zach Knox's avatar
Zach Knox committed
926
927
928
            let destDelegate = DeckTransitioningDelegate()
            destination.transitioningDelegate = destDelegate
            let tapped = sender as! FacilityCollectionViewCell //this is probably a bad idea just FYI future me
929
			destChild.facility = tapped.facility
Zach Knox's avatar
Zach Knox committed
930
931
932
933
934
935
936
937
938
939
940
941
942

            // 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.facilities = self.facilitiesArray
            filterView.filters = self.filters