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.9 KiB
226 lines
7.9 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 weak 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 { |
|
if let timezoneObject = TimeZone(identifier: identifier) { |
|
// Force-cast explicity since we get the identifier from `knownTimeZoneIdentifiers` |
|
let abbreviation = timezoneObject.abbreviation()! |
|
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.lowercased()) { |
|
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! |
|
}
|
|
|