You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

226 lines
7.7 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
import CoreModelKit
enum RowType {
case city
case timezone
}
enum SearchLocation {
case onboarding
case preferences
}
struct TimezoneMetadata {
let timezone: NSTimeZone
let tags: Set<String>
let formattedName: String
let abbreviation: String
}
class SearchDataSource: NSObject {
private var searchField: NSSearchField!
private var finalArray: [RowType] = []
private var location: SearchLocation = .preferences
private var dataTask: URLSessionDataTask? = .none
private var timezoneMetadataDictionary: [String: [String]] =
["GMT+5:30": ["india", "indian", "kolkata", "calcutta", "mumbai", "delhi", "hyderabad", "noida"],
"PST": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"],
"PDT": ["los", "los angeles", "california", "san francisco", "bay area", "pacific standard time"],
"UTC": ["utc", "universal"],
"EST": ["florida", "new york"],
"EDT": ["florida", "new york"]]
private var filteredArray: [TimezoneData] = [] // Filtered results from the Google API
private var timezoneArray: [TimezoneMetadata] = [] // All timezones
var timezoneFilteredArray: [TimezoneMetadata] = [] // Filtered timezones list based on search input
init(with searchField: NSSearchField, location: SearchLocation = .preferences) {
super.init()
self.searchField = searchField
self.location = location
setupTimezoneDatasource()
calculateChangesets()
}
func cleanupFilterArray() {
filteredArray = []
}
func setFilteredArrayValue(_ newArray: [TimezoneData]) {
filteredArray = newArray
}
func placeForRow(_ row: Int) -> RowType {
return finalArray[row]
}
// Returns result from finalArray based on the type i.e. city or timezone
func retrieveResult(_ row: Int) -> Any? {
let currentRowType = finalArray[row]
switch currentRowType {
case .timezone:
let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray
return datasource[row % datasource.count]
case .city:
if filteredArray.count == 0 {
return nil
}
let timezoneData = filteredArray[row % filteredArray.count]
return timezoneData
}
}
func retrieveFilteredResultFromGoogleAPI(_ index: Int) -> TimezoneData? {
if index >= filteredArray.count {
return nil
}
return filteredArray[index % filteredArray.count]
}
func resultsCount() -> Int {
return finalArray.count
}
private func setupTimezoneDatasource() {
timezoneArray = []
let anywhereOnEarth = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT-1200")!,
tags: ["aoe", "anywhere on earth"],
formattedName: "Anywhere on Earth",
abbreviation: "AOE")
let utcTimezone = TimezoneMetadata(timezone: NSTimeZone(abbreviation: "GMT")!,
tags: ["utc", "gmt", "universal"],
formattedName: "UTC",
abbreviation: "GMT")
timezoneArray.append(anywhereOnEarth)
timezoneArray.append(utcTimezone)
for identifier in TimeZone.knownTimeZoneIdentifiers {
guard let timezoneObject = TimeZone(identifier: identifier) else {
continue
}
let abbreviation = timezoneObject.abbreviation() ?? "Empty"
let identifier = timezoneObject.identifier
var tags: Set<String> = [abbreviation.lowercased(), identifier.lowercased()]
var extraTags: [String] = []
if let tagsPresent = timezoneMetadataDictionary[abbreviation] {
extraTags = tagsPresent
}
extraTags.forEach { tag in
tags.insert(tag)
}
let timezoneIdentifier = NSTimeZone(name: identifier)!
let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier,
tags: tags,
formattedName: identifier,
abbreviation: abbreviation)
timezoneArray.append(timezoneMetadata)
}
}
@discardableResult func calculateChangesets() -> Bool {
var changesets: [RowType] = []
func addTimezonesIfNeeded(_ data: [TimezoneMetadata]) {
data.forEach { _ in
changesets.append(.timezone)
}
}
if searchField.stringValue.isEmpty, location == .preferences {
addTimezonesIfNeeded(timezoneArray)
} else {
filteredArray.forEach { _ in
changesets.append(.city)
}
addTimezonesIfNeeded(timezoneFilteredArray)
}
if changesets != finalArray {
finalArray = changesets
return true
}
return false
}
func searchTimezones(_ searchString: String) {
timezoneFilteredArray = []
timezoneFilteredArray = timezoneArray.filter { timezoneMetadata -> Bool in
let tags = timezoneMetadata.tags
for tag in tags where tag.contains(searchString) {
return true
}
return false
}
}
func retrieveSelectedTimezone(_ selectedIndex: Int) -> TimezoneMetadata {
return searchField.stringValue.isEmpty == false ? timezoneFilteredArray[selectedIndex % timezoneFilteredArray.count] :
timezoneArray[selectedIndex]
}
}
extension SearchDataSource: NSTableViewDataSource {
func numberOfRows(in _: NSTableView) -> Int {
return finalArray.count
}
func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? {
let currentRowType = finalArray[row]
switch currentRowType {
case .timezone:
return timezoneCell(tableView, currentRowType, row)
case .city:
return cityCell(tableView, currentRowType, row)
}
}
}
extension SearchDataSource: NSTableViewDelegate {
func tableView(_: NSTableView, heightOfRow _: Int) -> CGFloat {
return 30
}
}
extension SearchDataSource {
private func timezoneCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? {
if let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell {
let datasource = searchField.stringValue.isEmpty ? timezoneArray : timezoneFilteredArray
guard !datasource.isEmpty else {
return nil
}
message.sourceName.stringValue = datasource[row % datasource.count].formattedName + " (\(datasource[row % datasource.count].abbreviation))"
return message
}
return nil
}
private func cityCell(_ tableView: NSTableView, _: RowType, _ row: Int) -> NSView? {
if let cityCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCell"), owner: self) as? SearchResultTableViewCell {
let timezoneData = filteredArray[row % filteredArray.count]
cityCell.sourceName.stringValue = timezoneData.formattedAddress ?? "Error"
return cityCell
}
return nil
}
}
class SearchResultTableViewCell: NSTableCellView {
@IBOutlet var sourceName: NSTextField!
}
class HeaderTableViewCell: NSTableCellView {
@IBOutlet var headerField: NSTextField!
}