Browse Source

Few changes.

pull/92/head
Abhishek Banthia 6 years ago
parent
commit
66ae394ecb
  1. 13
      .swiftlint.yml
  2. 15
      Clocker/AppDelegate.swift
  3. 29
      Clocker/Events and Reminders/CalendarHandler.swift
  4. 96
      Clocker/Onboarding/OnboardingParentViewController.swift
  5. 47
      Clocker/Onboarding/OnboardingSearchController.swift
  6. 12
      Clocker/Overall App/NetworkManager.swift
  7. 4
      Clocker/Overall App/Themer.swift
  8. 2
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  9. 33
      Clocker/Panel/Notes Popover/NotesPopover.swift
  10. 4
      Clocker/Panel/ParentPanelController.swift
  11. 5
      Clocker/Panel/UI/BackgroundPanelView.swift
  12. 6
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  13. 10
      Clocker/Preferences/Calendar/CalendarViewController.swift
  14. 133
      Clocker/Preferences/General/PreferencesViewController.swift
  15. 6
      Clocker/Preferences/OneWindowController.swift

13
.swiftlint.yml

@ -1,18 +1,15 @@
disabled_rules: # rule identifiers to exclude from running disabled_rules: # rule identifiers to exclude from running
- colon
- comma
- control_statement
- line_length
- type_body_length - type_body_length
- file_length # - file_length
- nesting # - nesting
- function_body_length
opt_in_rules: # some rules are only opt-in opt_in_rules: # some rules are only opt-in
- empty_count - empty_count
# included: # paths to include during linting. `--path` is ignored if present. # included: # paths to include during linting. `--path` is ignored if present.
# - Clocker # - Clocker
excluded: # paths to ignore during linting. Takes precedence over `included`. excluded: # paths to ignore during linting. Takes precedence over `included`.
- Clocker/Dependencies - Clocker/Dependencies
- Clocker/ClockerUnitTests
- Clocker/ClockerUITests
# - Pods # - Pods
# - Source/ExcludedFolder # - Source/ExcludedFolder
# - Source/ExcludedFile.swift # - Source/ExcludedFile.swift
@ -51,4 +48,4 @@ identifier_name:
# - id # - id
# - URL # - URL
# - GlobalAPIKey # - GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) reporter: "xcode"

15
Clocker/AppDelegate.swift

@ -2,7 +2,7 @@
import Cocoa import Cocoa
open class AppDelegate : NSObject, NSApplicationDelegate { open class AppDelegate: NSObject, NSApplicationDelegate {
lazy private var floatingWindow: FloatingWindowController = FloatingWindowController.shared() lazy private var floatingWindow: FloatingWindowController = FloatingWindowController.shared()
lazy private var panelController: PanelController = PanelController.shared() lazy private var panelController: PanelController = PanelController.shared()
@ -13,7 +13,7 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
panelObserver?.invalidate() panelObserver?.invalidate()
} }
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if let path = keyPath, path == "values.globalPing" { if let path = keyPath, path == "values.globalPing" {
@ -98,7 +98,8 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
}() }()
private func showOnboardingFlow() { private func showOnboardingFlow() {
let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty) || (ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument)) let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty)
|| (ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument))
shouldLaunchOnboarding ? controller?.launch() : continueUsually() shouldLaunchOnboarding ? controller?.launch() : continueUsually()
} }
@ -238,10 +239,16 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
} }
} }
let informativeText = """
Clocker must be run from the Applications folder in order to work properly.
Please quit Clocker, move it to the Applications folder, and relaunch.
Current folder: \(applicationDirectory)"
"""
// Clocker is installed out of Applications directory // Clocker is installed out of Applications directory
// This breaks start at login! Time to show an alert and terminate // This breaks start at login! Time to show an alert and terminate
showAlert(message: "Move Clocker to the Applications folder", showAlert(message: "Move Clocker to the Applications folder",
informativeText: "Clocker must be run from the Applications folder in order to work properly.\n\nPlease quit Clocker, move it to the Applications folder, and relaunch. Current folder: \(applicationDirectory)", informativeText: informativeText,
buttonTitle: "Quit") buttonTitle: "Quit")
// Terminate // Terminate

29
Clocker/Events and Reminders/CalendarHandler.swift

@ -228,23 +228,11 @@ extension EventCenter {
} }
nextDate = autoupdatingCalendar.startOfDay(for: nextDate) nextDate = autoupdatingCalendar.startOfDay(for: nextDate)
// Make a customized struct
let isStartDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.startDate) && (event.endDate.compare(date) == .orderedDescending)
let isEndDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.endDate) && (event.startDate.compare(date) == .orderedAscending)
let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame)
let isSingleDay = event.isAllDay && (event.startDate.compare(date) == .orderedSame && event.endDate.compare(nextDate) == .orderedSame)
let eventInfo = EventInfo(event: event,
isStartDate: isStartDate,
isEndDate: isEndDate,
isAllDay: isAllDay,
isSingleDay: isSingleDay)
if eventsForDateMapper[date] == nil { if eventsForDateMapper[date] == nil {
eventsForDateMapper[date] = [] eventsForDateMapper[date] = []
} }
eventsForDateMapper[date]?.append(eventInfo) eventsForDateMapper[date]?.append(generateEventInfo(for: event, date, nextDate))
date = nextDate date = nextDate
} }
@ -265,6 +253,21 @@ extension EventCenter {
filterEvents() filterEvents()
} }
private func generateEventInfo(for event: EKEvent, _ date: Date, _ nextDate: Date) -> EventInfo {
// Make a customized struct
let isStartDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.startDate) && (event.endDate.compare(date) == .orderedDescending)
let isEndDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.endDate) && (event.startDate.compare(date) == .orderedAscending)
let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame)
let isSingleDay = event.isAllDay && (event.startDate.compare(date) == .orderedSame && event.endDate.compare(nextDate) == .orderedSame)
let eventInfo = EventInfo(event: event,
isStartDate: isStartDate,
isEndDate: isEndDate,
isAllDay: isAllDay,
isSingleDay: isSingleDay)
return eventInfo
}
} }
struct CalendarInfo { struct CalendarInfo {

96
Clocker/Onboarding/OnboardingParentViewController.swift

@ -26,15 +26,15 @@ class OnboardingParentViewController: NSViewController {
@IBOutlet private var backButton: NSButton! @IBOutlet private var backButton: NSButton!
@IBOutlet private var positiveButton: NSButton! @IBOutlet private var positiveButton: NSButton!
private lazy var welcomeVC: WelcomeViewController? = (storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.welcomeIdentifier) as? WelcomeViewController) private lazy var welcomeVC = (storyboard?.instantiateController(withIdentifier: .welcomeIdentifier) as? WelcomeViewController)
private lazy var permissionsVC: OnboardingPermissionsViewController? = (storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.onboardingPermissionsIdentifier) as? OnboardingPermissionsViewController) private lazy var permissionsVC = (storyboard?.instantiateController(withIdentifier: .onboardingPermissionsIdentifier) as? OnboardingPermissionsViewController)
private lazy var startAtLoginVC: StartAtLoginViewController? = (storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.startAtLoginIdentifier) as? StartAtLoginViewController) private lazy var startAtLoginVC = (storyboard?.instantiateController(withIdentifier: .startAtLoginIdentifier) as? StartAtLoginViewController)
private lazy var onboardingSearchVC: OnboardingSearchController? = (self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.onboardingSearchIdentifier) as? OnboardingSearchController) private lazy var onboardingSearchVC = (storyboard?.instantiateController(withIdentifier: .onboardingSearchIdentifier) as? OnboardingSearchController)
private lazy var finalOnboardingVC: FinalOnboardingViewController? = (self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.finalOnboardingIdentifier) as? FinalOnboardingViewController) private lazy var finalOnboardingVC = (storyboard?.instantiateController(withIdentifier: .finalOnboardingIdentifier) as? FinalOnboardingViewController)
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -96,6 +96,19 @@ class OnboardingParentViewController: NSViewController {
@IBAction func continueOnboarding(_: NSButton) { @IBAction func continueOnboarding(_: NSButton) {
if positiveButton.tag == OnboardingType.welcome.rawValue { if positiveButton.tag == OnboardingType.welcome.rawValue {
navigateToPermissions()
} else if positiveButton.tag == OnboardingType.permissions.rawValue {
navigateToStartAtLogin()
} else if positiveButton.tag == OnboardingType.launchAtLogin.rawValue {
navigateToSearch()
} else if positiveButton.tag == OnboardingType.search.rawValue {
navigateToFinalStage()
} else {
performFinalStepsBeforeFinishing()
}
}
private func navigateToPermissions() {
guard let fromViewController = welcomeVC, let toViewController = permissionsVC else { guard let fromViewController = welcomeVC, let toViewController = permissionsVC else {
assertionFailure() assertionFailure()
return return
@ -110,8 +123,9 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.backButton.isHidden = false self.backButton.isHidden = false
} }
}
} else if positiveButton.tag == OnboardingType.permissions.rawValue { private func navigateToStartAtLogin() {
guard let fromViewController = permissionsVC, let toViewController = startAtLoginVC else { guard let fromViewController = permissionsVC, let toViewController = startAtLoginVC else {
assertionFailure() assertionFailure()
return return
@ -127,7 +141,9 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Open Clocker At Login" self.positiveButton.title = "Open Clocker At Login"
self.negativeButton.isHidden = false self.negativeButton.isHidden = false
} }
} else if positiveButton.tag == OnboardingType.launchAtLogin.rawValue { }
private func navigateToSearch() {
guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else { guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else {
assertionFailure() assertionFailure()
return return
@ -145,7 +161,9 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
} }
} else if positiveButton.tag == OnboardingType.search.rawValue { }
private func navigateToFinalStage() {
guard let fromViewController = onboardingSearchVC, let toViewController = finalOnboardingVC else { guard let fromViewController = onboardingSearchVC, let toViewController = finalOnboardingVC else {
assertionFailure() assertionFailure()
return return
@ -161,8 +179,9 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Launch Clocker" self.positiveButton.title = "Launch Clocker"
} }
} else { }
private func performFinalStepsBeforeFinishing() {
self.positiveButton.tag = OnboardingType.complete.rawValue self.positiveButton.tag = OnboardingType.complete.rawValue
// Install the menubar option! // Install the menubar option!
@ -175,7 +194,6 @@ class OnboardingParentViewController: NSViewController {
UserDefaults.standard.set(true, forKey: CLShowOnboardingFlow) UserDefaults.standard.set(true, forKey: CLShowOnboardingFlow)
} }
} }
}
private func addChildIfNeccessary(_ viewController: NSViewController) { private func addChildIfNeccessary(_ viewController: NSViewController) {
if children.contains(viewController) == false { if children.contains(viewController) == false {
@ -185,22 +203,18 @@ class OnboardingParentViewController: NSViewController {
@IBAction func back(_: Any) { @IBAction func back(_: Any) {
if backButton.tag == OnboardingType.welcome.rawValue { if backButton.tag == OnboardingType.welcome.rawValue {
guard let fromViewController = permissionsVC, let toViewController = welcomeVC else { goBackToWelcomeScreen()
assertionFailure() } else if backButton.tag == OnboardingType.permissions.rawValue {
return goBackToPermissions()
} else if backButton.tag == OnboardingType.launchAtLogin.rawValue {
goBackToStartAtLogin()
} else if backButton.tag == OnboardingType.search.rawValue {
goBackToSearch()
} }
transition(from: fromViewController,
to: toViewController,
options: .slideRight) {
self.positiveButton.tag = OnboardingType.welcome.rawValue
self.backButton.isHidden = true
self.positiveButton.title = "Get Started"
} }
} else if backButton.tag == OnboardingType.permissions.rawValue {
// We're on StartAtLogin VC and we have to go back to Permissions
guard let fromViewController = startAtLoginVC, let toViewController = permissionsVC else { private func goBackToSearch() {
guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else {
assertionFailure() assertionFailure()
return return
} }
@ -208,12 +222,14 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.permissions.rawValue self.positiveButton.tag = OnboardingType.search.rawValue
self.backButton.tag = OnboardingType.welcome.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue
self.negativeButton.isHidden = true
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true
} }
} else if backButton.tag == OnboardingType.launchAtLogin.rawValue { }
private func goBackToStartAtLogin() {
guard let fromViewController = onboardingSearchVC, let toViewController = startAtLoginVC else { guard let fromViewController = onboardingSearchVC, let toViewController = startAtLoginVC else {
assertionFailure() assertionFailure()
return return
@ -227,9 +243,12 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Open Clocker At Login" self.positiveButton.title = "Open Clocker At Login"
self.negativeButton.isHidden = false self.negativeButton.isHidden = false
} }
} else if backButton.tag == OnboardingType.search.rawValue { }
guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else { private func goBackToPermissions() {
// We're on StartAtLogin VC and we have to go back to Permissions
guard let fromViewController = startAtLoginVC, let toViewController = permissionsVC else {
assertionFailure() assertionFailure()
return return
} }
@ -237,12 +256,25 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.search.rawValue self.positiveButton.tag = OnboardingType.permissions.rawValue
self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.backButton.tag = OnboardingType.welcome.rawValue
self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
self.positiveButton.title = "Continue"
}
} }
private func goBackToWelcomeScreen() {
guard let fromViewController = permissionsVC, let toViewController = welcomeVC else {
assertionFailure()
return
}
transition(from: fromViewController,
to: toViewController,
options: .slideRight) {
self.positiveButton.tag = OnboardingType.welcome.rawValue
self.backButton.isHidden = true
self.positiveButton.title = "Get Started"
} }
} }

47
Clocker/Onboarding/OnboardingSearchController.swift

@ -200,7 +200,13 @@ class OnboardingSearchController: NSViewController {
} }
} }
private var placeholders: [String] = ["New York", "Los Angeles", "Chicago", "Moscow", "Tokyo", "Istanbul", "Beijing", "Shanghai", "Sao Paulo", "Cairo", "Mexico City", "London", "Seoul", "Copenhagen", "Tel Aviv", "Bern", "San Francisco", "Los Angeles", "Sydney NSW", "Berlin"] private var placeholders: [String] = ["New York", "Los Angeles", "Chicago",
"Moscow", "Tokyo", "Istanbul",
"Beijing", "Shanghai", "Sao Paulo",
"Cairo", "Mexico City", "London",
"Seoul", "Copenhagen", "Tel Aviv",
"Bern", "San Francisco", "Los Angeles",
"Sydney NSW", "Berlin"]
private func setup() { private func setup() {
appName.stringValue = "Quick Add Locations" appName.stringValue = "Quick Add Locations"
@ -278,12 +284,7 @@ class OnboardingSearchController: NSViewController {
self.results = [] self.results = []
if let errorPresent = error { if let errorPresent = error {
if errorPresent.localizedDescription == PreferencesConstants.offlineErrorMessage { self.presentErrorMessage(errorPresent.localizedDescription)
self.setInfoLabel(PreferencesConstants.noInternetConnectivityError)
} else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage)
}
setupForError() setupForError()
return return
} }
@ -302,11 +303,29 @@ class OnboardingSearchController: NSViewController {
return return
} }
for result in searchResults!.results { self.appendResultsToFilteredArray(searchResults!.results)
let location = result.geometry.location
self.setInfoLabel(CLEmptyString)
self.resultsTableView.reloadData()
}
})
}
private func presentErrorMessage(_ errorMessage: String) {
if errorMessage == PreferencesConstants.offlineErrorMessage {
self.setInfoLabel(PreferencesConstants.noInternetConnectivityError)
} else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage)
}
}
private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) {
results.forEach {
let location = $0.geometry.location
let latitude = location.lat let latitude = location.lat
let longitude = location.lng let longitude = location.lng
let formattedAddress = result.formattedAddress let formattedAddress = $0.formattedAddress
let totalPackage = [ let totalPackage = [
"latitude": latitude, "latitude": latitude,
@ -314,17 +333,11 @@ class OnboardingSearchController: NSViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId CLPlaceIdentifier: $0.placeId
] as [String: Any] ] as [String: Any]
self.results.append(TimezoneData(with: totalPackage)) self.results.append(TimezoneData(with: totalPackage))
} }
self.setInfoLabel(CLEmptyString)
self.resultsTableView.reloadData()
}
})
} }
// Extracting this out for tests // Extracting this out for tests

12
Clocker/Overall App/NetworkManager.swift

@ -10,15 +10,23 @@ class NetworkManager: NSObject {
}() }()
static let internalServerError: NSError = { static let internalServerError: NSError = {
let localizedError = """
There was a problem retrieving your information. Please try again later.
If the problem continues please contact App Support.
"""
let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Internal Error", let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Internal Error",
NSLocalizedFailureReasonErrorKey: "There was a problem retrieving your information. Please try again later. If the problem continues please contact App Support."] NSLocalizedFailureReasonErrorKey: localizedError]
let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary) let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary)
return error return error
}() }()
static let unableToGenerateURL: NSError = { static let unableToGenerateURL: NSError = {
let localizedError = """
There was a problem searching the location. Please try again later.
If the problem continues please contact App Support.
"""
let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Unable to generate URL", let userInfoDictionary: [String: Any] = [NSLocalizedDescriptionKey: "Unable to generate URL",
NSLocalizedFailureReasonErrorKey: "There was a problem searching the location. Please try again later. If the problem continues please contact App Support."] NSLocalizedFailureReasonErrorKey: localizedError]
let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary) let error = NSError(domain: "APIError", code: 100, userInfo: userInfoDictionary)
return error return error
}() }()

4
Clocker/Overall App/Themer.swift

@ -432,6 +432,8 @@ extension Themer {
} }
} }
return themeIndex == .light ? NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) : NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) return themeIndex == .light ?
NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) :
NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0)
} }
} }

2
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -168,7 +168,7 @@ extension TimezoneDataOperations {
return "\(todaysDate(with: sliderValue))\(timeDifference())" return "\(todaysDate(with: sliderValue))\(timeDifference())"
} }
let errorDictionary: [String: Any] = ["Timezone" : dataObject.timezone(), let errorDictionary: [String: Any] = ["Timezone": dataObject.timezone(),
"Current Locale": Locale.autoupdatingCurrent.identifier, "Current Locale": Locale.autoupdatingCurrent.identifier,
"Slider Value": sliderValue, "Slider Value": sliderValue,
"Today's Date": Date()] "Today's Date": Date()]

33
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -278,34 +278,24 @@ class NotesPopover: NSViewController {
private func insertTimezoneInDefaultPreferences() { private func insertTimezoneInDefaultPreferences() {
guard let model = dataObject, var timezones = timezoneObjects else { return } guard let model = dataObject, var timezones = timezoneObjects else { return }
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: model) let encodedObject = NSKeyedArchiver.archivedData(withRootObject: model)
timezones[currentRow] = encodedObject timezones[currentRow] = encodedObject
DataStore.shared().setTimezones(timezones) DataStore.shared().setTimezones(timezones)
} }
private func updateMenubarTitles() { private func updateMenubarTitles() {
guard let model = dataObject, model.isFavourite == 1, var timezones = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] else { return } guard let model = dataObject, model.isFavourite == 1, var timezones = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] else { return }
let menubarIndex = timezones.firstIndex { (menubarLocation) -> Bool in let menubarIndex = timezones.firstIndex { (menubarLocation) -> Bool in
if let convertedObject = TimezoneData.customObject(from: menubarLocation) { if let convertedObject = TimezoneData.customObject(from: menubarLocation) {
return convertedObject.isEqual(dataObject) return convertedObject.isEqual(dataObject)
} }
return false return false
} }
if let index = menubarIndex { if let index = menubarIndex {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: model) let encodedObject = NSKeyedArchiver.archivedData(withRootObject: model)
timezones[index] = encodedObject timezones[index] = encodedObject
UserDefaults.standard.set(timezones, forKey: CLMenubarFavorites) UserDefaults.standard.set(timezones, forKey: CLMenubarFavorites)
} }
} }
@ -370,7 +360,6 @@ class NotesPopover: NSViewController {
if eventCenter.reminderAccessNotDetermined() { if eventCenter.reminderAccessNotDetermined() {
eventCenter.requestAccess(to: .reminder, completionHandler: { granted in eventCenter.requestAccess(to: .reminder, completionHandler: { granted in
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.createReminder() self.createReminder()
@ -402,7 +391,6 @@ class NotesPopover: NSViewController {
private func createReminder() { private func createReminder() {
guard let model = dataObject else { return } guard let model = dataObject else { return }
if setReminderCheckbox.state == .on { if setReminderCheckbox.state == .on {
let eventCenter = EventCenter.sharedCenter() let eventCenter = EventCenter.sharedCenter()
let alertIndex = alertPopupButton.indexOfSelectedItem let alertIndex = alertPopupButton.indexOfSelectedItem
@ -507,29 +495,19 @@ class NotesPopover: NSViewController {
} }
setInitialReminderTime() setInitialReminderTime()
updateTimeFormat() updateTimeFormat()
updateSecondsFormat() updateSecondsFormat()
} }
private func updateTimeFormat() { private func updateTimeFormat() {
if dataObject?.overrideFormat.rawValue == 0 { if let overrideFormat = dataObject?.overrideFormat.rawValue {
timeFormatControl.setSelected(true, forSegment: 0) timeFormatControl.setSelected(true, forSegment: overrideFormat)
} else if dataObject?.overrideFormat.rawValue == 1 {
timeFormatControl.setSelected(true, forSegment: 1)
} else {
timeFormatControl.setSelected(true, forSegment: 2)
} }
} }
private func updateSecondsFormat() { private func updateSecondsFormat() {
if dataObject?.overrideSecondsFormat.rawValue == 0 { if let overrideFormat = dataObject?.overrideSecondsFormat.rawValue {
secondsFormatControl.setSelected(true, forSegment: 0) secondsFormatControl.setSelected(true, forSegment: overrideFormat)
} else if dataObject?.overrideSecondsFormat.rawValue == 1 {
secondsFormatControl.setSelected(true, forSegment: 1)
} else {
secondsFormatControl.setSelected(true, forSegment: 2)
} }
} }
@ -549,13 +527,10 @@ extension NotesPopover: NSTextFieldDelegate {
// We need to do a couple of things if the customLabel is updated // We need to do a couple of things if the customLabel is updated
// 1. Update the userDefaults // 1. Update the userDefaults
// 2. Check if the timezone is displayed in the menubar; if so, update the model // 2. Check if the timezone is displayed in the menubar; if so, update the model
guard let model = dataObject else { return } guard let model = dataObject else { return }
model.setLabel(customLabel.stringValue) model.setLabel(customLabel.stringValue)
insertTimezoneInDefaultPreferences() insertTimezoneInDefaultPreferences()
updateMenubarTitles() updateMenubarTitles()
NotificationCenter.default.post(name: NSNotification.Name.customLabelChanged, NotificationCenter.default.post(name: NSNotification.Name.customLabelChanged,

4
Clocker/Panel/ParentPanelController.swift

@ -536,7 +536,9 @@ class ParentPanelController: NSWindowController {
stride(from: 0, to: preferences.count, by: 1).forEach { stride(from: 0, to: preferences.count, by: 1).forEach {
let current = preferences[$0] let current = preferences[$0]
if $0 < mainTableView.numberOfRows, let cellView = mainTableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? TimezoneCellView, let model = TimezoneData.customObject(from: current) { if $0 < mainTableView.numberOfRows,
let cellView = mainTableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? TimezoneCellView,
let model = TimezoneData.customObject(from: current) {
if let futureSliderCell = futureSlider.cell as? CustomSliderCell, futureSliderCell.tracking == true { if let futureSliderCell = futureSlider.cell as? CustomSliderCell, futureSliderCell.tracking == true {
return return
} }

5
Clocker/Panel/UI/BackgroundPanelView.swift

@ -40,7 +40,10 @@ class BackgroundPanelView: NSView {
let xOrdinate = arrowMidX - BackgroundPanelConstants.kArrowHeight - curveOffset let xOrdinate = arrowMidX - BackgroundPanelConstants.kArrowHeight - curveOffset
let yOrdinate = frame.height - BackgroundPanelConstants.kArrowHeight - BackgroundPanelConstants.kBorderWidth let yOrdinate = frame.height - BackgroundPanelConstants.kArrowHeight - BackgroundPanelConstants.kBorderWidth
arrowPath.move(to: NSPoint(x: xOrdinate, y: yOrdinate)) arrowPath.move(to: NSPoint(x: xOrdinate, y: yOrdinate))
arrowPath.relativeCurve(to: NSPoint(x: BackgroundPanelConstants.kArrowHeight + curveOffset, y: BackgroundPanelConstants.kBorderWidth), controlPoint1: NSPoint(x: curveOffset, y: 0), controlPoint2: NSPoint(x: BackgroundPanelConstants.kArrowHeight, y: BackgroundPanelConstants.kArrowHeight)) arrowPath.relativeCurve(to: NSPoint(x: BackgroundPanelConstants.kArrowHeight + curveOffset,
y: BackgroundPanelConstants.kBorderWidth),
controlPoint1: NSPoint(x: curveOffset, y: 0),
controlPoint2: NSPoint(x: BackgroundPanelConstants.kArrowHeight, y: BackgroundPanelConstants.kArrowHeight))
} }
Themer.shared().mainBackgroundColor().setFill() Themer.shared().mainBackgroundColor().setFill()

6
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -112,7 +112,11 @@ class AppearanceViewController: ParentViewController {
menubarDisplayOptionsLabel.stringValue = "Menubar Display Options" menubarDisplayOptionsLabel.stringValue = "Menubar Display Options"
menubarModeLabel.stringValue = "Menubar Mode" menubarModeLabel.stringValue = "Menubar Mode"
[headerLabel, timeFormatLabel, panelTheme, dayDisplayOptionsLabel, showSliderLabel, showSecondsLabel, showSunriseLabel, largerTextLabel, futureSliderRangeLabel, includeDayLabel, includeDateLabel, includePlaceLabel, menubarDisplayOptionsLabel, appDisplayLabel, menubarModeLabel].forEach { [headerLabel, timeFormatLabel, panelTheme,
dayDisplayOptionsLabel, showSliderLabel, showSecondsLabel,
showSunriseLabel, largerTextLabel, futureSliderRangeLabel,
includeDayLabel, includeDateLabel, includePlaceLabel,
menubarDisplayOptionsLabel, appDisplayLabel, menubarModeLabel].forEach {
$0?.textColor = Themer.shared().mainTextColor() $0?.textColor = Themer.shared().mainTextColor()
} }
} }

10
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -224,7 +224,9 @@ class CalendarViewController: ParentViewController {
showEventsFromLabel.stringValue = "Show events from" showEventsFromLabel.stringValue = "Show events from"
truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""
[headerLabel, upcomingEventView, allDayMeetingsLabel, showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel, showEventsFromLabel, charactersField, truncateAccessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() } [headerLabel, upcomingEventView, allDayMeetingsLabel,
showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel,
showEventsFromLabel, charactersField, truncateAccessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() }
} }
} }
@ -253,12 +255,14 @@ extension CalendarViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let currentSource = calendars[row] as? String, let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell { if let currentSource = calendars[row] as? String,
let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell {
message.sourceName.stringValue = currentSource message.sourceName.stringValue = currentSource
return message return message
} }
if let currentSource = calendars[row] as? CalendarInfo, let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell { if let currentSource = calendars[row] as? CalendarInfo,
let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell {
calendarCell.calendarName.stringValue = currentSource.calendar.title calendarCell.calendarName.stringValue = currentSource.calendar.title
calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off
calendarCell.calendarSelected.target = self calendarCell.calendarSelected.target = self

133
Clocker/Preferences/General/PreferencesViewController.swift

@ -479,7 +479,10 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
UserDefaults.standard.set(filteredMenubars, forKey: CLMenubarFavorites) UserDefaults.standard.set(filteredMenubars, forKey: CLMenubarFavorites)
if let appDelegate = NSApplication.shared.delegate as? AppDelegate, let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.isEmpty, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { if let appDelegate = NSApplication.shared.delegate as? AppDelegate,
let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data],
menubarFavourites.isEmpty,
DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false {
appDelegate.invalidateMenubarTimer(true) appDelegate.invalidateMenubarTimer(true)
} }
@ -532,10 +535,15 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
// Time to display the alert. // Time to display the alert.
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
let infoText = """
Multiple timezones occupy space and if macOS determines Clocker is occupying too much space, it'll hide Clocker entirely!
Enable Menubar Compact Mode to fit in more timezones in less space.
"""
let alert = NSAlert() let alert = NSAlert()
alert.showsSuppressionButton = true alert.showsSuppressionButton = true
alert.messageText = "More than one location added to the menubar 😅" alert.messageText = "More than one location added to the menubar 😅"
alert.informativeText = "Multiple timezones occupy space and if macOS determines Clocker is occupying too much space, it'll hide Clocker entirely! Enable Menubar Compact Mode to fit in more timezones in less space." alert.informativeText = infoText
alert.addButton(withTitle: "Enable Compact Mode") alert.addButton(withTitle: "Enable Compact Mode")
alert.addButton(withTitle: "Cancel") alert.addButton(withTitle: "Cancel")
@ -626,13 +634,21 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
} }
if tableColumn.identifier.rawValue == "formattedAddress" { if tableColumn.identifier.rawValue == "formattedAddress" {
return arePlacesSortedInAscendingOrder ? object1.formattedAddress! > object2.formattedAddress! : object1.formattedAddress! < object2.formattedAddress! return arePlacesSortedInAscendingOrder ?
object1.formattedAddress! > object2.formattedAddress! :
object1.formattedAddress! < object2.formattedAddress!
} else { } else {
return arePlacesSortedInAscendingOrder ? object1.customLabel! > object2.customLabel! : object1.customLabel! < object2.customLabel! return arePlacesSortedInAscendingOrder ?
object1.customLabel! > object2.customLabel! :
object1.customLabel! < object2.customLabel!
} }
} }
arePlacesSortedInAscendingOrder ? timezoneTableView.setIndicatorImage(NSImage(named: NSImage.Name("NSDescendingSortIndicator"))!, in: tableColumn) : timezoneTableView.setIndicatorImage(NSImage(named: NSImage.Name("NSAscendingSortIndicator"))!, in: tableColumn) let indicatorImage = arePlacesSortedInAscendingOrder ?
NSImage(named: NSImage.Name("NSDescendingSortIndicator"))! :
NSImage(named: NSImage.Name("NSAscendingSortIndicator"))!
timezoneTableView.setIndicatorImage(indicatorImage, in: tableColumn)
arePlacesSortedInAscendingOrder.toggle() arePlacesSortedInAscendingOrder.toggle()
@ -645,7 +661,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
extension PreferencesViewController { extension PreferencesViewController {
@objc private func search() { @objc private func search() {
var searchString = searchField.stringValue let searchString = searchField.stringValue
if searchString.isEmpty { if searchString.isEmpty {
dataTask?.cancel() dataTask?.cancel()
@ -657,8 +673,6 @@ extension PreferencesViewController {
dataTask?.cancel() dataTask?.cancel()
} }
let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US"
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if self.availableTimezoneTableView.isHidden { if self.availableTimezoneTableView.isHidden {
self.availableTimezoneTableView.isHidden = false self.availableTimezoneTableView.isHidden = false
@ -675,26 +689,14 @@ extension PreferencesViewController {
self.placeholderLabel.placeholderString = "Searching for \(searchString)" self.placeholderLabel.placeholderString = "Searching for \(searchString)"
let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines) self.dataTask = NetworkManager.task(with: self.generateSearchURL(),
searchString = words.joined(separator: CLEmptyString)
let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(CLGeocodingKey)&language=\(userPreferredLanguage)"
self.dataTask = NetworkManager.task(with: urlString,
completionHandler: { [weak self] response, error in completionHandler: { [weak self] response, error in
guard let `self` = self else { return } guard let `self` = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if let errorPresent = error { if let errorPresent = error {
if errorPresent.localizedDescription == PreferencesConstants.offlineErrorMessage { self.presentError(errorPresent.localizedDescription)
self.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else {
self.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
}
self.isActivityInProgress = false
return return
} }
@ -711,11 +713,41 @@ extension PreferencesViewController {
return return
} }
for result in searchResults!.results { self.appendResultsToFilteredArray(searchResults!.results)
let location = result.geometry.location self.prepareUIForPresentingResults()
}
})
}
}
private func generateSearchURL() -> String {
let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US"
var searchString = searchField.stringValue
let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines)
searchString = words.joined(separator: CLEmptyString)
let url = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(CLGeocodingKey)&language=\(userPreferredLanguage)"
return url
}
private func presentError(_ errorMessage: String) {
if errorMessage == PreferencesConstants.offlineErrorMessage {
self.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else {
self.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
}
self.isActivityInProgress = false
}
private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) {
results.forEach {
let location = $0.geometry.location
let latitude = location.lat let latitude = location.lat
let longitude = location.lng let longitude = location.lng
let formattedAddress = result.formattedAddress let formattedAddress = $0.formattedAddress
let totalPackage = [ let totalPackage = [
"latitude": latitude, "latitude": latitude,
@ -723,23 +755,19 @@ extension PreferencesViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId CLPlaceIdentifier: $0.placeId
] as [String: Any] ] as [String: Any]
self.filteredArray.append(TimezoneData(with: totalPackage)) self.filteredArray.append(TimezoneData(with: totalPackage))
} }
}
private func prepareUIForPresentingResults() {
self.placeholderLabel.placeholderString = CLEmptyString self.placeholderLabel.placeholderString = CLEmptyString
self.isActivityInProgress = false self.isActivityInProgress = false
self.availableTimezoneTableView.reloadData() self.availableTimezoneTableView.reloadData()
} }
})
}
}
// Extracting this out for tests // Extracting this out for tests
private func decode(from data: Data) -> SearchResult? { private func decode(from data: Data) -> SearchResult? {
let jsonDecoder = JSONDecoder() let jsonDecoder = JSONDecoder()
@ -802,6 +830,25 @@ extension PreferencesViewController {
if error == nil, let json = response, let timezone = self.decodeTimezone(from: json) { if error == nil, let json = response, let timezone = self.decodeTimezone(from: json) {
if self.availableTimezoneTableView.selectedRow >= 0 && self.availableTimezoneTableView.selectedRow < self.filteredArray.count { if self.availableTimezoneTableView.selectedRow >= 0 && self.availableTimezoneTableView.selectedRow < self.filteredArray.count {
self.installTimezone(timezone)
}
self.updateViewState()
} else {
OperationQueue.main.addOperation {
if error?.localizedDescription == "The Internet connection appears to be offline." {
self.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else {
self.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
}
self.isActivityInProgress = false
}
}
}
}
}
private func installTimezone(_ timezone: Timezone) {
guard let dataObject = self.filteredArray[self.availableTimezoneTableView.selectedRow] as? TimezoneData else { guard let dataObject = self.filteredArray[self.availableTimezoneTableView.selectedRow] as? TimezoneData else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return return
@ -830,22 +877,6 @@ extension PreferencesViewController {
Logger.log(object: ["PlaceName": filteredAddress, "Timezone": timezone.timeZoneId], for: "Filtered Address") Logger.log(object: ["PlaceName": filteredAddress, "Timezone": timezone.timeZoneId], for: "Filtered Address")
} }
self.updateViewState()
} else {
OperationQueue.main.addOperation {
if error?.localizedDescription == "The Internet connection appears to be offline." {
self.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else {
self.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
}
self.isActivityInProgress = false
}
}
}
}
}
private func resetStateAndShowDisconnectedMessage() { private func resetStateAndShowDisconnectedMessage() {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.showMessage() self.showMessage()
@ -969,6 +1000,11 @@ extension PreferencesViewController {
} }
} else { } else {
cleanupAfterInstallingTimezone()
}
}
private func cleanupAfterInstallingTimezone() {
let data = TimezoneData() let data = TimezoneData()
data.setLabel(CLEmptyString) data.setLabel(CLEmptyString)
@ -1018,7 +1054,6 @@ extension PreferencesViewController {
isActivityInProgress = false isActivityInProgress = false
} }
}
private func metadata(for selection: String) -> (String, String) { private func metadata(for selection: String) -> (String, String) {
if selection == "Anywhere on Earth" { if selection == "Anywhere on Earth" {

6
Clocker/Preferences/OneWindowController.swift

@ -35,7 +35,7 @@ class OneWindowController: NSWindowController {
NSAnimationContext.runAnimationGroup({ (context) in NSAnimationContext.runAnimationGroup({ (context) in
context.duration = 1 context.duration = 1
context.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeOut) context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
self.window?.animator().backgroundColor = Themer.shared().mainBackgroundColor() self.window?.animator().backgroundColor = Themer.shared().mainBackgroundColor()
}) })
@ -60,7 +60,7 @@ class OneWindowController: NSWindowController {
} }
class func shared() -> OneWindowController { class func shared() -> OneWindowController {
if (sharedWindow == nil) { if sharedWindow == nil {
let prefStoryboard = NSStoryboard.init(name: "Preferences", bundle: nil) let prefStoryboard = NSStoryboard.init(name: "Preferences", bundle: nil)
sharedWindow = prefStoryboard.instantiateInitialController() as? OneWindowController sharedWindow = prefStoryboard.instantiateInitialController() as? OneWindowController
} }
@ -95,7 +95,7 @@ class OneWindowController: NSWindowController {
tabViewController.tabViewItems.forEach { (tabViewItem) in tabViewController.tabViewItems.forEach { (tabViewItem) in
let identity = (tabViewItem.identifier as? String) ?? "" let identity = (tabViewItem.identifier as? String) ?? ""
if (identifierTOImageMapping[identity] != nil) { if identifierTOImageMapping[identity] != nil {
tabViewItem.image = identifierTOImageMapping[identity] tabViewItem.image = identifierTOImageMapping[identity]
} }
} }

Loading…
Cancel
Save