Browse Source

Add timezones to search results during Onboarding!

pull/92/head
Abhishek 4 years ago
parent
commit
54a55c15d4
  1. 169
      Clocker/Onboarding/OnboardingSearchController.swift
  2. 37
      Clocker/Preferences/General/PreferencesViewController.swift
  3. 41
      Clocker/Preferences/General/SearchDataSource.swift

169
Clocker/Onboarding/OnboardingSearchController.swift

@ -12,7 +12,7 @@ class OnboardingSearchController: NSViewController {
@IBOutlet private var accessoryLabel: NSTextField!
@IBOutlet var undoButton: NSButton!
private var results: [TimezoneData] = []
private var searchResultsDataSource: SearchDataSource!
private var dataTask: URLSessionDataTask? = .none
private var themeDidChangeNotification: NSObjectProtocol?
@ -31,6 +31,9 @@ class OnboardingSearchController: NSViewController {
view.wantsLayer = true
searchResultsDataSource = SearchDataSource(with: searchBar, location: .onboarding)
resultsTableView.isHidden = true
resultsTableView.delegate = self
resultsTableView.setAccessibility("ResultsTableView")
resultsTableView.dataSource = self
@ -68,23 +71,68 @@ class OnboardingSearchController: NSViewController {
@objc func doubleClickAction(_: NSTableView?) {
[accessoryLabel].forEach { $0?.isHidden = false }
if resultsTableView.selectedRow >= 0, resultsTableView.selectedRow < results.count {
let selectedTimezone = results[resultsTableView.selectedRow]
if resultsTableView.selectedRow >= 0, resultsTableView.selectedRow < searchResultsDataSource.resultsCount() {
let selectedType = searchResultsDataSource.placeForRow(resultsTableView.selectedRow)
switch selectedType {
case .city:
let filteredGoogleResult = searchResultsDataSource.retrieveFilteredResultFromGoogleAPI(resultsTableView.selectedRow)
addTimezoneToDefaults(filteredGoogleResult!)
return
case .timezone:
cleanupAfterInstallingTimezone()
return
}
}
}
private func cleanupAfterInstallingTimezone() {
let data = TimezoneData()
data.setLabel(CLEmptyString)
let currentSelection = searchResultsDataSource.retrieveSelectedTimezone(resultsTableView.selectedRow)
let metaInfo = metadata(for: currentSelection)
data.timezoneID = metaInfo.0.name
data.formattedAddress = metaInfo.1.formattedName
data.selectionType = .timezone
data.isSystemTimezone = metaInfo.0.name == NSTimeZone.system.identifier
let operationObject = TimezoneDataOperations(with: data)
operationObject.saveObject()
searchResultsDataSource.cleanupFilterArray()
searchResultsDataSource.timezoneFilteredArray = []
searchResultsDataSource.calculateChangesets()
searchBar.stringValue = CLEmptyString
accessoryLabel.stringValue = "Added \(metaInfo.1.formattedName)."
undoButton.isHidden = false
setupLabelHidingTimer()
resultsTableView.reloadData()
resultsTableView.isHidden = true
}
addTimezoneToDefaults(selectedTimezone)
private func metadata(for selection: TimezoneMetadata) -> (NSTimeZone, TimezoneMetadata) {
if selection.formattedName == "Anywhere on Earth" {
return (NSTimeZone(name: "GMT-1200")!, selection)
} else if selection.formattedName == "UTC" {
return (NSTimeZone(name: "GMT")!, selection)
} else {
return (selection.timezone, selection)
}
}
private func addTimezoneToDefaults(_ timezone: TimezoneData) {
func setupLabelHidingTimer() {
Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in
OperationQueue.main.addOperation {
self.accessoryLabel.stringValue = CLEmptyString
}
private func setupLabelHidingTimer() {
Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in
OperationQueue.main.addOperation {
self.setInfoLabel(CLEmptyString)
}
}
}
private func addTimezoneToDefaults(_ timezone: TimezoneData) {
if resultsTableView.selectedRow == -1 {
setInfoLabel(PreferencesConstants.noTimezoneSelectedErrorMessage)
setupLabelHidingTimer()
@ -131,22 +179,10 @@ class OnboardingSearchController: NSViewController {
return false
}
// Extracting this out for tests
private func decodeTimezone(from data: Data) -> Timezone? {
let jsonDecoder = JSONDecoder()
do {
let decodedObject = try jsonDecoder.decode(Timezone.self, from: data)
return decodedObject
} catch {
Logger.info("decodedObject error: \n\(error)")
return nil
}
}
private func fetchTimezone(for latitude: Double, and longitude: Double, _ dataObject: TimezoneData) {
if NetworkManager.isConnected() == false || ProcessInfo.processInfo.arguments.contains("mockTimezoneDown") {
setInfoLabel(PreferencesConstants.noInternetConnectivityError)
results = []
searchResultsDataSource.cleanupFilterArray()
resultsTableView.reloadData()
return
}
@ -166,8 +202,8 @@ class OnboardingSearchController: NSViewController {
return
}
if error == nil, let json = response, let response = self.decodeTimezone(from: json) {
if self.resultsTableView.selectedRow >= 0, self.resultsTableView.selectedRow < self.results.count {
if error == nil, let json = response, let response = json.decodeTimezone() {
if self.resultsTableView.selectedRow >= 0, self.resultsTableView.selectedRow < self.searchResultsDataSource.resultsCount() {
var filteredAddress = "Error"
if let address = dataObject.formattedAddress {
@ -250,11 +286,6 @@ class OnboardingSearchController: NSViewController {
accessoryLabel.isHidden = false
if NetworkManager.isConnected() == false {
setInfoLabel(PreferencesConstants.noInternetConnectivityError)
return
}
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(OnboardingSearchController.actualSearch), with: nil, afterDelay: 0.2)
}
@ -268,6 +299,7 @@ class OnboardingSearchController: NSViewController {
@objc func actualSearch() {
func setupForError() {
searchResultsDataSource.calculateChangesets()
resultsTableView.isHidden = true
}
@ -300,11 +332,18 @@ class OnboardingSearchController: NSViewController {
return
}
self.results = []
self.searchResultsDataSource.cleanupFilterArray()
self.searchResultsDataSource.timezoneFilteredArray = []
if let errorPresent = error {
self.presentErrorMessage(errorPresent.localizedDescription)
setupForError()
self.findLocalSearchResultsForTimezones()
if self.searchResultsDataSource.timezoneFilteredArray.count == 0 {
self.presentErrorMessage(errorPresent.localizedDescription)
setupForError()
return
}
self.prepareUIForPresentingResults()
return
}
@ -314,7 +353,7 @@ class OnboardingSearchController: NSViewController {
return
}
let searchResults = self.decode(from: data)
let searchResults = data.decode()
if searchResults?.status == "ZERO_RESULTS" {
self.setInfoLabel("No results! 😔 Try entering the exact name.")
@ -323,10 +362,8 @@ class OnboardingSearchController: NSViewController {
}
self.appendResultsToFilteredArray(searchResults!.results)
self.setInfoLabel(CLEmptyString)
self.resultsTableView.reloadData()
self.findLocalSearchResultsForTimezones()
self.prepareUIForPresentingResults()
}
})
}
@ -339,12 +376,25 @@ class OnboardingSearchController: NSViewController {
}
}
private func findLocalSearchResultsForTimezones() {
let lowercasedSearchString = searchBar.stringValue.lowercased()
searchResultsDataSource.searchTimezones(lowercasedSearchString)
}
private func prepareUIForPresentingResults() {
setInfoLabel(CLEmptyString)
if searchResultsDataSource.calculateChangesets() {
resultsTableView.isHidden = false
resultsTableView.reloadData()
}
}
private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) {
results.forEach {
let location = $0.geometry.location
let finalTimezones: [TimezoneData] = results.map { (result) -> TimezoneData in
let location = result.geometry.location
let latitude = location.lat
let longitude = location.lng
let formattedAddress = $0.formattedAddress
let formattedAddress = result.formattedAddress
let totalPackage = [
"latitude": latitude,
@ -352,27 +402,17 @@ class OnboardingSearchController: NSViewController {
CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: $0.placeId,
CLPlaceIdentifier: result.placeId,
] as [String: Any]
self.results.append(TimezoneData(with: totalPackage))
return TimezoneData(with: totalPackage)
}
}
// Extracting this out for tests
private func decode(from data: Data) -> SearchResult? {
let jsonDecoder = JSONDecoder()
do {
let decodedObject = try jsonDecoder.decode(SearchResult.self, from: data)
return decodedObject
} catch {
Logger.info("decodedObject error: \n\(error)")
return nil
}
searchResultsDataSource.setFilteredArrayValue(finalTimezones)
}
private func resetSearchView() {
results = []
searchResultsDataSource.cleanupFilterArray()
resultsTableView.reloadData()
searchBar.stringValue = CLEmptyString
searchBar.placeholderString = "Press Enter to Search"
@ -386,13 +426,18 @@ class OnboardingSearchController: NSViewController {
extension OnboardingSearchController: NSTableViewDataSource {
func numberOfRows(in _: NSTableView) -> Int {
return results.count
return searchResultsDataSource.resultsCount()
}
func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? {
if let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCellView"), owner: self) as? ResultTableViewCell, row >= 0, row < results.count {
let currentTimezone = results[row]
result.result.stringValue = " \(currentTimezone.formattedAddress ?? "Place Name")"
if let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "resultCellView"), owner: self) as? ResultTableViewCell, row >= 0, row < searchResultsDataSource.resultsCount() {
let currentSelection = searchResultsDataSource.retrieveResult(row)
if let timezone = currentSelection as? TimezoneMetadata {
result.result.stringValue = " \(timezone.formattedName)"
} else if let location = currentSelection as? TimezoneData {
result.result.stringValue = " \(location.formattedAddress ?? "Place Name")"
}
result.result.textColor = Themer.shared().mainTextColor()
return result
}
@ -403,7 +448,7 @@ extension OnboardingSearchController: NSTableViewDataSource {
extension OnboardingSearchController: NSTableViewDelegate {
func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat {
if row == 0, results.isEmpty {
if row == 0, searchResultsDataSource.resultsCount() == 0 {
return 30
}
@ -411,7 +456,7 @@ extension OnboardingSearchController: NSTableViewDelegate {
}
func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool {
return results.isEmpty ? row != 0 : true
return searchResultsDataSource.resultsCount() == 0 ? row != 0 : true
}
func tableView(_: NSTableView, rowViewForRow _: Int) -> NSTableRowView? {

37
Clocker/Preferences/General/PreferencesViewController.swift

@ -438,7 +438,7 @@ extension PreferencesViewController {
return
}
let searchResults = self.decode(from: data)
let searchResults = data.decode()
if searchResults?.status == "ZERO_RESULTS" {
self.findLocalSearchResultsForTimezones()
@ -460,8 +460,6 @@ extension PreferencesViewController {
private func findLocalSearchResultsForTimezones() {
let lowercasedSearchString = searchField.stringValue.lowercased()
searchResultsDataSource.searchTimezones(lowercasedSearchString)
Logger.info(searchResultsDataSource.timezoneFilteredArray.debugDescription)
}
private func generateSearchURL() -> String {
@ -520,30 +518,6 @@ extension PreferencesViewController {
}
}
// Extracting this out for tests
private func decode(from data: Data) -> SearchResult? {
let jsonDecoder = JSONDecoder()
do {
let decodedObject = try jsonDecoder.decode(SearchResult.self, from: data)
return decodedObject
} catch {
Logger.info("decodedObject error: \n\(error)")
return nil
}
}
// Extracting this out for tests
private func decodeTimezone(from data: Data) -> Timezone? {
let jsonDecoder = JSONDecoder()
do {
let decodedObject = try jsonDecoder.decode(Timezone.self, from: data)
return decodedObject
} catch {
Logger.info("decodedObject error: \n\(error)")
return nil
}
}
private func resetSearchView() {
if dataTask?.state == .running {
dataTask?.cancel()
@ -576,7 +550,7 @@ extension PreferencesViewController {
return
}
if error == nil, let json = response, let timezone = strongSelf.decodeTimezone(from: json) {
if error == nil, let json = response, let timezone = json.decodeTimezone() {
if strongSelf.availableTimezoneTableView.selectedRow >= 0 {
strongSelf.installTimezone(timezone)
}
@ -597,7 +571,7 @@ extension PreferencesViewController {
}
private func installTimezone(_ timezone: Timezone) {
guard let dataObject = searchResultsDataSource.retrieveFilteredResult(availableTimezoneTableView.selectedRow) else {
guard let dataObject = searchResultsDataSource.retrieveFilteredResultFromGoogleAPI(availableTimezoneTableView.selectedRow) else {
assertionFailure("Data was unexpectedly nil")
return
}
@ -742,7 +716,7 @@ extension PreferencesViewController {
}
private func cleanupAfterInstallingCity() {
guard let dataObject = searchResultsDataSource.retrieveFilteredResult(availableTimezoneTableView.selectedRow) else {
guard let dataObject = searchResultsDataSource.retrieveFilteredResultFromGoogleAPI(availableTimezoneTableView.selectedRow) else {
assertionFailure("Data was unexpectedly nil")
return
}
@ -763,8 +737,7 @@ extension PreferencesViewController {
let data = TimezoneData()
data.setLabel(CLEmptyString)
let currentSelection = searchResultsDataSource.retrieveSelectedTimezone(searchField.stringValue,
availableTimezoneTableView.selectedRow)
let currentSelection = searchResultsDataSource.retrieveSelectedTimezone(availableTimezoneTableView.selectedRow)
let metaInfo = metadata(for: currentSelection)
data.timezoneID = metaInfo.0.name

41
Clocker/Preferences/General/SearchDataSource.swift

@ -8,6 +8,11 @@ enum RowType {
case timezone
}
enum SearchLocation {
case onboarding
case preferences
}
struct TimezoneMetadata {
let timezone: NSTimeZone
let tags: Set<String>
@ -18,6 +23,7 @@ struct TimezoneMetadata {
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"],
@ -27,13 +33,14 @@ class SearchDataSource: NSObject {
"EST": ["florida", "new york"],
"EDT": ["florida", "new york"]]
private var filteredArray: [TimezoneData] = []
private var timezoneArray: [TimezoneMetadata] = []
var timezoneFilteredArray: [TimezoneMetadata] = []
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) {
init(with searchField: NSSearchField, location: SearchLocation = .preferences) {
super.init()
self.searchField = searchField
self.location = location
setupTimezoneDatasource()
calculateChangesets()
}
@ -50,10 +57,28 @@ class SearchDataSource: NSObject {
return finalArray[row]
}
func retrieveFilteredResult(_ index: Int) -> TimezoneData? {
// 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:
let timezoneData = filteredArray[row % filteredArray.count]
return timezoneData
}
}
func retrieveFilteredResultFromGoogleAPI(_ index: Int) -> TimezoneData? {
return filteredArray[index % filteredArray.count]
}
func resultsCount() -> Int {
return finalArray.count
}
private func setupTimezoneDatasource() {
timezoneArray = []
@ -103,7 +128,7 @@ class SearchDataSource: NSObject {
}
}
if searchField.stringValue.isEmpty {
if searchField.stringValue.isEmpty, location == .preferences {
addTimezonesIfNeeded(timezoneArray)
} else {
filteredArray.forEach { _ in
@ -132,8 +157,8 @@ class SearchDataSource: NSObject {
}
}
func retrieveSelectedTimezone(_ searchString: String, _ selectedIndex: Int) -> TimezoneMetadata {
return searchString.isEmpty == false ? timezoneFilteredArray[selectedIndex % timezoneFilteredArray.count] :
func retrieveSelectedTimezone(_ selectedIndex: Int) -> TimezoneMetadata {
return searchField.stringValue.isEmpty == false ? timezoneFilteredArray[selectedIndex % timezoneFilteredArray.count] :
timezoneArray[selectedIndex]
}
}

Loading…
Cancel
Save