Browse Source

Linting.

pull/92/head
Abhishek Banthia 6 years ago
parent
commit
3b54a5ccf3
  1. 51
      .swiftlint.yml
  2. 130
      Clocker/AppDelegate.swift
  3. 4
      Clocker/Clocker/LocationController.swift
  4. 8
      Clocker/ClockerUITests/AboutUsTests.swift
  5. 2
      Clocker/ClockerUITests/FloatingWindowTests.swift
  6. 33
      Clocker/ClockerUITests/NetworkDisconnectionTests.swift
  7. 86
      Clocker/ClockerUITests/OnboardingTests.swift
  8. 10
      Clocker/ClockerUITests/PanelTests.swift
  9. 82
      Clocker/ClockerUITests/PreferencesTest.swift
  10. 14
      Clocker/ClockerUITests/ReviewTests.swift
  11. 28
      Clocker/ClockerUITests/ShortcutTests.swift
  12. 104
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  13. 2
      Clocker/ClockerUnitTests/RateTests.swift
  14. 3
      Clocker/Dependencies/Date Additions/Constants.swift
  15. 3
      Clocker/Dependencies/Date Additions/Date+Bundle.swift
  16. 162
      Clocker/Dependencies/Date Additions/Date+Comparators.swift
  17. 71
      Clocker/Dependencies/Date Additions/Date+Components.swift
  18. 25
      Clocker/Dependencies/Date Additions/Date+Format.swift
  19. 24
      Clocker/Dependencies/Date Additions/Date+Inits.swift
  20. 40
      Clocker/Dependencies/Date Additions/Date+Manipulations.swift
  21. 191
      Clocker/Dependencies/Date Additions/Date+TimeAgo.swift
  22. 1
      Clocker/Dependencies/Date Additions/Enums.swift
  23. 18
      Clocker/Dependencies/Date Additions/Integer+DateTools.swift
  24. 63
      Clocker/Dependencies/Date Additions/TimeChunk.swift
  25. 204
      Clocker/Dependencies/Date Additions/TimePeriod.swift
  26. 64
      Clocker/Dependencies/Date Additions/TimePeriodChain.swift
  27. 56
      Clocker/Dependencies/Date Additions/TimePeriodCollection.swift
  28. 43
      Clocker/Dependencies/Date Additions/TimePeriodGroup.swift
  29. 99
      Clocker/Dependencies/Solar.swift
  30. 12
      Clocker/Events and Reminders/CalendarHandler.swift
  31. 24
      Clocker/Menu Bar/MenubarHandler.swift
  32. 66
      Clocker/Menu Bar/StatusContainerView.swift
  33. 24
      Clocker/Menu Bar/StatusItemHandler.swift
  34. 31
      Clocker/Menu Bar/StatusItemView.swift
  35. 2
      Clocker/Onboarding/FinalOnboardingViewController.swift
  36. 2
      Clocker/Onboarding/OnboardingController.swift
  37. 32
      Clocker/Onboarding/OnboardingParentViewController.swift
  38. 10
      Clocker/Onboarding/OnboardingPermissionsViewController.swift
  39. 88
      Clocker/Onboarding/OnboardingSearchController.swift
  40. 64
      Clocker/Overall App/AppDefaults.swift
  41. 18
      Clocker/Overall App/AppKit + Additions.swift
  42. 22
      Clocker/Overall App/DataStore.swift
  43. 6
      Clocker/Overall App/DateFormatterManager.swift
  44. 2
      Clocker/Overall App/NetworkManager.swift
  45. 34
      Clocker/Overall App/Themer.swift
  46. 26
      Clocker/Panel/Data Layer/TimezoneData.swift
  47. 52
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  48. 38
      Clocker/Panel/Notes Popover/NotesPopover.swift
  49. 2
      Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift
  50. 32
      Clocker/Panel/PanelController.swift
  51. 22
      Clocker/Panel/ParentPanelController.swift
  52. 26
      Clocker/Panel/Rate Controller/RateController.swift
  53. 2
      Clocker/Panel/UI/PanelTableView.swift
  54. 4
      Clocker/Panel/UI/TimezoneCellView.swift
  55. 22
      Clocker/Preferences/About/AboutViewController.swift
  56. 2
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  57. 57
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  58. 95
      Clocker/Preferences/Calendar/CalendarViewController.swift
  59. 62
      Clocker/Preferences/General/PreferencesViewController.swift
  60. 40
      Clocker/Preferences/OneWindowController.swift
  61. 4
      Clocker/Preferences/ParentViewController.swift
  62. 10
      Clocker/Preferences/Permissions/PermissionsViewController.swift
  63. 533
      xcodebuild.log

51
.swiftlint.yml

@ -0,0 +1,51 @@
disabled_rules: # rule identifiers to exclude from running
- colon
- comma
- control_statement
opt_in_rules: # some rules are only opt-in
- empty_count
# Find all the available rules by running:
# swiftlint rules
# included: # paths to include during linting. `--path` is ignored if present.
# - Clocker
# excluded: # paths to ignore during linting. Takes precedence over `included`.
# - Carthage
# - Pods
# - Source/ExcludedFolder
# - Source/ExcludedFile.swift
# - Source/*/ExcludedFile.swift # Exclude files with a wildcard
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
- explicit_self
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 110
# they can set both implicitly with an array
type_body_length:
- 300 # warning
- 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
min_length: 3 # only warning
max_length: # warning and error
warning: 40
error: 50
excluded: iPhone # excluded via string
identifier_name:
min_length: # only min_length
error: 3 # only error
# excluded: # excluded via string array
# - id
# - URL
# - GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)

130
Clocker/AppDelegate.swift

@ -3,60 +3,60 @@
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()
private var statusBarHandler: StatusItemHandler! private var statusBarHandler: StatusItemHandler!
deinit { deinit {
panelController.removeObserver(self, forKeyPath: "hasActivePanel") panelController.removeObserver(self, forKeyPath: "hasActivePanel")
} }
private var kContextActivePanel = 0 private var kContextActivePanel = 0
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 context == &kContextActivePanel { if context == &kContextActivePanel {
statusBarHandler.setHasActiveIcon(panelController.hasActivePanelGetter()) statusBarHandler.setHasActiveIcon(panelController.hasActivePanelGetter())
} else if let path = keyPath, path == "values.globalPing" { } else if let path = keyPath, path == "values.globalPing" {
let hotKeyCenter = PTHotKeyCenter.shared() let hotKeyCenter = PTHotKeyCenter.shared()
// Unregister old hot key // Unregister old hot key
let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path) let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path)
hotKeyCenter?.unregisterHotKey(oldHotKey) hotKeyCenter?.unregisterHotKey(oldHotKey)
// We don't register unless there's a valid key combination // We don't register unless there's a valid key combination
guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else { guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else {
return return
} }
// Register new key // Register new key
let newHotKey: PTHotKey = PTHotKey(identifier: keyPath, let newHotKey: PTHotKey = PTHotKey(identifier: keyPath,
keyCombo: newShortcut, keyCombo: newShortcut,
target: self, target: self,
action: #selector(ping(_:))) action: #selector(ping(_:)))
hotKeyCenter?.register(newHotKey) hotKeyCenter?.register(newHotKey)
} }
} }
public func applicationWillFinishLaunching(_ notification: Notification) { public func applicationWillFinishLaunching(_ notification: Notification) {
iVersion.sharedInstance().useAllAvailableLanguages = true iVersion.sharedInstance().useAllAvailableLanguages = true
iVersion.sharedInstance().verboseLogging = false iVersion.sharedInstance().verboseLogging = false
} }
public func applicationDidFinishLaunching(_ notification: Notification) { public func applicationDidFinishLaunching(_ notification: Notification) {
// Initializing the event store takes really long // Initializing the event store takes really long
EventCenter.sharedCenter() EventCenter.sharedCenter()
AppDefaults.initialize() AppDefaults.initialize()
// Check if we can show the onboarding flow! // Check if we can show the onboarding flow!
showOnboardingFlow() showOnboardingFlow()
// Ratings Controller initialization // Ratings Controller initialization
RateController.applicationDidLaunch(UserDefaults.standard) RateController.applicationDidLaunch(UserDefaults.standard)
@ -66,26 +66,26 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
checkIfRunFromApplicationsFolder() checkIfRunFromApplicationsFolder()
#endif #endif
} }
public func applicationDockMenu(_ sender: NSApplication) -> NSMenu? { public func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
let menu = NSMenu(title: "Quick Access") let menu = NSMenu(title: "Quick Access")
Logger.log(object: ["Dock Menu Triggered": "YES"], for: "Dock Menu Triggered") Logger.log(object: ["Dock Menu Triggered": "YES"], for: "Dock Menu Triggered")
let toggleMenuItem = NSMenuItem(title: "Toggle Panel", action: #selector(AppDelegate.togglePanel(_:)), keyEquivalent: "") let toggleMenuItem = NSMenuItem(title: "Toggle Panel", action: #selector(AppDelegate.togglePanel(_:)), keyEquivalent: "")
let openPreferences = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.openPreferencesWindow), keyEquivalent: ",") let openPreferences = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.openPreferencesWindow), keyEquivalent: ",")
[toggleMenuItem, openPreferences].forEach { [toggleMenuItem, openPreferences].forEach {
$0.isEnabled = true $0.isEnabled = true
menu.addItem($0) menu.addItem($0)
} }
return menu return menu
} }
@objc private func openPreferencesWindow() { @objc private func openPreferencesWindow() {
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground) let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground)
if displayMode == 1 { if displayMode == 1 {
let floatingWindow = FloatingWindowController.shared() let floatingWindow = FloatingWindowController.shared()
floatingWindow.openPreferences(NSButton()) floatingWindow.openPreferences(NSButton())
@ -94,7 +94,7 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
panelController.openPreferences(NSButton()) panelController.openPreferences(NSButton())
} }
} }
private lazy var controller: OnboardingController? = { private lazy var controller: OnboardingController? = {
let s = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil) let s = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil)
return s.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController return s.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController
@ -102,42 +102,42 @@ 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()
} }
func continueUsually() { func continueUsually() {
// Check if another instance of the app is already running. If so, then stop this one. // Check if another instance of the app is already running. If so, then stop this one.
checkIfAppIsAlreadyOpen() checkIfAppIsAlreadyOpen()
// Make sure the old models are not used anymore // Make sure the old models are not used anymore
TimezoneData.convert() TimezoneData.convert()
// Install the menubar item! // Install the menubar item!
statusBarHandler = StatusItemHandler() statusBarHandler = StatusItemHandler()
if UserDefaults.standard.object(forKey: CLInstallHomeIndicatorObject) == nil { if UserDefaults.standard.object(forKey: CLInstallHomeIndicatorObject) == nil {
fetchLocalTimezone() fetchLocalTimezone()
UserDefaults.standard.set(1, forKey: CLInstallHomeIndicatorObject) UserDefaults.standard.set(1, forKey: CLInstallHomeIndicatorObject)
} }
if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) { if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) {
RateController.setPreviewMode(true) RateController.setPreviewMode(true)
} }
UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true]) UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true])
assignShortcut() assignShortcut()
panelController.addObserver(self, panelController.addObserver(self,
forKeyPath: "hasActivePanel", forKeyPath: "hasActivePanel",
options: [.new], options: [.new],
context: &kContextActivePanel) context: &kContextActivePanel)
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
setActivationPolicy() setActivationPolicy()
// Set the display mode default as panel! // Set the display mode default as panel!
if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? NSNumber, displayMode.intValue == 1 { if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? NSNumber, displayMode.intValue == 1 {
showFloatingWindow() showFloatingWindow()
@ -145,22 +145,22 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
showFloatingWindow() showFloatingWindow()
} }
} }
// Should we have a dock icon or just stay in the menubar? // Should we have a dock icon or just stay in the menubar?
private func setActivationPolicy() { private func setActivationPolicy() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
let activationPolicy: NSApplication.ActivationPolicy = defaults.integer(forKey: CLAppDislayOptions) == 0 ? .accessory : .regular let activationPolicy: NSApplication.ActivationPolicy = defaults.integer(forKey: CLAppDislayOptions) == 0 ? .accessory : .regular
NSApp.setActivationPolicy(activationPolicy) NSApp.setActivationPolicy(activationPolicy)
} }
private func checkIfAppIsAlreadyOpen() { private func checkIfAppIsAlreadyOpen() {
guard let bundleID = Bundle.main.bundleIdentifier else { guard let bundleID = Bundle.main.bundleIdentifier else {
return return
} }
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID)
if apps.count > 1 { if apps.count > 1 {
let currentApplication = NSRunningApplication.current let currentApplication = NSRunningApplication.current
for app in apps where app != currentApplication { for app in apps where app != currentApplication {
@ -168,13 +168,13 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
} }
} }
} }
private func showAppAlreadyOpenMessage() { private func showAppAlreadyOpenMessage() {
showAlert(message: "An instance of Clocker is already open 😅", showAlert(message: "An instance of Clocker is already open 😅",
informativeText: "This instance of Clocker will terminate now.", informativeText: "This instance of Clocker will terminate now.",
buttonTitle: "Close") buttonTitle: "Close")
} }
private func showAlert(message: String, informativeText: String, buttonTitle: String) { private func showAlert(message: String, informativeText: String, buttonTitle: String) {
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert() let alert = NSAlert()
@ -183,24 +183,24 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
alert.addButton(withTitle: buttonTitle) alert.addButton(withTitle: buttonTitle)
alert.runModal() alert.runModal()
} }
private func fetchLocalTimezone() { private func fetchLocalTimezone() {
let identifier = TimeZone.autoupdatingCurrent.identifier let identifier = TimeZone.autoupdatingCurrent.identifier
let currentTimezone = TimezoneData() let currentTimezone = TimezoneData()
currentTimezone.timezoneID = identifier currentTimezone.timezoneID = identifier
currentTimezone.setLabel(identifier) currentTimezone.setLabel(identifier)
currentTimezone.formattedAddress = identifier currentTimezone.formattedAddress = identifier
currentTimezone.isSystemTimezone = true currentTimezone.isSystemTimezone = true
currentTimezone.placeID = "Home" currentTimezone.placeID = "Home"
let operations = TimezoneDataOperations(with: currentTimezone) let operations = TimezoneDataOperations(with: currentTimezone)
operations.saveObject(at: 0) operations.saveObject(at: 0)
// Retrieve Location // Retrieve Location
// retrieveLatestLocation() // retrieveLatestLocation()
} }
@IBAction func ping(_ sender: Any) { @IBAction func ping(_ sender: Any) {
togglePanel(sender) togglePanel(sender)
} }
@ -209,29 +209,29 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
let locationController = LocationController.sharedController() let locationController = LocationController.sharedController()
locationController.determineAndRequestLocationAuthorization() locationController.determineAndRequestLocationAuthorization()
} }
private func showFloatingWindow() { private func showFloatingWindow() {
// Display the Floating Window! // Display the Floating Window!
floatingWindow.showWindow(nil) floatingWindow.showWindow(nil)
floatingWindow.updateTableContent() floatingWindow.updateTableContent()
floatingWindow.startWindowTimer() floatingWindow.startWindowTimer()
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
} }
private func assignShortcut() { private func assignShortcut() {
NSUserDefaultsController.shared.addObserver(self, NSUserDefaultsController.shared.addObserver(self,
forKeyPath: "values.globalPing", forKeyPath: "values.globalPing",
options: [.initial, .new], options: [.initial, .new],
context: nil) context: nil)
} }
private func checkIfRunFromApplicationsFolder() { private func checkIfRunFromApplicationsFolder() {
if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true { if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true {
return return
} }
let bundlePath = Bundle.main.bundlePath let bundlePath = Bundle.main.bundlePath
let applicationDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationDirectory, let applicationDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationDirectory,
FileManager.SearchPathDomainMask.localDomainMask, FileManager.SearchPathDomainMask.localDomainMask,
@ -241,21 +241,21 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
return return
} }
} }
// 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: "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)",
buttonTitle: "Quit") buttonTitle: "Quit")
// Terminate // Terminate
NSApp.terminate(nil) NSApp.terminate(nil)
} }
@IBAction open func togglePanel(_ sender: Any) { @IBAction open func togglePanel(_ sender: Any) {
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground) let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground)
if displayMode == 1 { if displayMode == 1 {
floatingWindow.showWindow(nil) floatingWindow.showWindow(nil)
floatingWindow.updateTableContent() floatingWindow.updateTableContent()
@ -271,23 +271,23 @@ open class AppDelegate : NSObject, NSApplicationDelegate {
open func setupFloatingWindow() { open func setupFloatingWindow() {
showFloatingWindow() showFloatingWindow()
} }
open func closeFloatingWindow() { open func closeFloatingWindow() {
floatingWindow.window?.close() floatingWindow.window?.close()
} }
func statusItemForPanel() -> StatusItemHandler { func statusItemForPanel() -> StatusItemHandler {
return statusBarHandler return statusBarHandler
} }
open func setPanelDefaults() { open func setPanelDefaults() {
panelController.updateDefaultPreferences() panelController.updateDefaultPreferences()
} }
open func setupMenubarTimer() { open func setupMenubarTimer() {
statusBarHandler.setupStatusItem() statusBarHandler.setupStatusItem()
} }
open func invalidateMenubarTimer(_ showIcon: Bool) { open func invalidateMenubarTimer(_ showIcon: Bool) {
statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true) statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true)
} }

4
Clocker/Clocker/LocationController.swift

@ -89,7 +89,7 @@ class LocationController: NSObject {
extension LocationController: CLLocationManagerDelegate { extension LocationController: CLLocationManagerDelegate {
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard locations.count > 0, let coordinates = locations.first?.coordinate else { return } guard locations.count > 0, let coordinates = locations.first?.coordinate else { return }
let reverseGeoCoder = CLGeocoder() let reverseGeoCoder = CLGeocoder()
@ -99,7 +99,7 @@ extension LocationController: CLLocationManagerDelegate {
guard let customLabel = placemarks?.first?.locality else { return } guard let customLabel = placemarks?.first?.locality else { return }
self.updateHomeObject(with: customLabel, coordinates: coordinates) self.updateHomeObject(with: customLabel, coordinates: coordinates)
self.locationManager.stopUpdatingLocation() self.locationManager.stopUpdatingLocation()
} }
} }

8
Clocker/ClockerUITests/AboutUsTests.swift

@ -29,17 +29,17 @@ class AboutUsTests: XCTestCase {
func testMockingFeedback() { func testMockingFeedback() {
app.tapMenubarIcon() app.tapMenubarIcon()
app.buttons["Preferences"].click() app.buttons["Preferences"].click()
tapAboutTab() tapAboutTab()
let expectedVersion = "Clocker 1.6.09 (64)" let expectedVersion = "Clocker 1.6.09 (64)"
guard let presentVersion = app.windows["Clocker"].staticTexts["ClockerVersion"].value as? String else { guard let presentVersion = app.windows["Clocker"].staticTexts["ClockerVersion"].value as? String else {
XCTFail("Present version not present") XCTFail("Present version not present")
return return
} }
XCTAssertEqual(expectedVersion, presentVersion) XCTAssertEqual(expectedVersion, presentVersion)
app.checkBoxes["ClockerPrivateFeedback"].click() app.checkBoxes["ClockerPrivateFeedback"].click()
app.buttons["Send Feedback"].click() app.buttons["Send Feedback"].click()

2
Clocker/ClockerUITests/FloatingWindowTests.swift

@ -88,7 +88,7 @@ class FloatingWindowTests: XCTestCase {
let floatingSlider = app.sliders["FloatingSlider"].exists let floatingSlider = app.sliders["FloatingSlider"].exists
app.buttons["FloatingPreferences"].click() app.buttons["FloatingPreferences"].click()
let appearanceTab = app.toolbars.buttons.element(boundBy: 1) let appearanceTab = app.toolbars.buttons.element(boundBy: 1)
appearanceTab.click() appearanceTab.click()

33
Clocker/ClockerUITests/NetworkDisconnectionTests.swift

@ -3,18 +3,18 @@
import XCTest import XCTest
class NetworkDisconnectionTests: XCTestCase { class NetworkDisconnectionTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
super.setUp() super.setUp()
continueAfterFailure = false continueAfterFailure = false
app = XCUIApplication() app = XCUIApplication()
} }
func precondition() { func precondition() {
app.launch() app.launch()
if !app.tables["FloatingTableView"].exists { if !app.tables["FloatingTableView"].exists {
app.tapMenubarIcon() app.tapMenubarIcon()
app.buttons["Pin"].click() app.buttons["Pin"].click()
@ -25,38 +25,38 @@ class NetworkDisconnectionTests: XCTestCase {
app.launchArguments.append("mockNetworkDown") app.launchArguments.append("mockNetworkDown")
precondition() precondition()
app.buttons["FloatingPreferences"].click() app.buttons["FloatingPreferences"].click()
if app.sheets.count == 0 { if app.sheets.count == 0 {
app.windows["Clocker"].checkBoxes["AddTimezone"].click() app.windows["Clocker"].checkBoxes["AddTimezone"].click()
} }
XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists) XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists)
let searchField = app.searchFields["AvailableSearchField"] let searchField = app.searchFields["AvailableSearchField"]
searchField.reset(text: "Uganda") searchField.reset(text: "Uganda")
sleep(1) sleep(1)
XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists) XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists)
app.sheets.buttons["Close"].click() app.sheets.buttons["Close"].click()
} }
func testFetchingATimezone() { func testFetchingATimezone() {
app.launchArguments.append("mockTimezoneDown") app.launchArguments.append("mockTimezoneDown")
precondition() precondition()
app.buttons["FloatingPreferences"].click() app.buttons["FloatingPreferences"].click()
if app.sheets.count == 0 { if app.sheets.count == 0 {
app.windows["Clocker"].checkBoxes["AddTimezone"].click() app.windows["Clocker"].checkBoxes["AddTimezone"].click()
} }
XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists) XCTAssertFalse(app.sheets.staticTexts["ErrorPlaceholder"].exists)
let searchField = app.searchFields["AvailableSearchField"] let searchField = app.searchFields["AvailableSearchField"]
searchField.reset(text: "Uganda") searchField.reset(text: "Uganda")
let firstResult = app.tables["AvailableTimezoneTableView"].tableRows.firstMatch let firstResult = app.tables["AvailableTimezoneTableView"].tableRows.firstMatch
let waiter = XCTWaiter() let waiter = XCTWaiter()
let isHittable = NSPredicate(format: "exists == true", "") let isHittable = NSPredicate(format: "exists == true", "")
let addExpectation = expectation(for: isHittable, let addExpectation = expectation(for: isHittable,
@ -65,11 +65,10 @@ class NetworkDisconnectionTests: XCTestCase {
waiter.wait(for: [addExpectation], timeout: 5) waiter.wait(for: [addExpectation], timeout: 5)
app.tables["AvailableTimezoneTableView"].click() app.tables["AvailableTimezoneTableView"].click()
app.buttons["AddAvailableTimezone"].click() app.buttons["AddAvailableTimezone"].click()
sleep(1) sleep(1)
XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists) XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists)
app.sheets.buttons["Close"].click() app.sheets.buttons["Close"].click()
} }
} }

86
Clocker/ClockerUITests/OnboardingTests.swift

@ -5,7 +5,7 @@ import XCTest
let CLOnboaringTestsLaunchArgument = "isTestingTheOnboardingFlow" let CLOnboaringTestsLaunchArgument = "isTestingTheOnboardingFlow"
class OnboardingTests: XCTestCase { class OnboardingTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
@ -19,142 +19,142 @@ class OnboardingTests: XCTestCase {
// 1. The flow (forward button and back button take the user to the correct screen) // 1. The flow (forward button and back button take the user to the correct screen)
// 2. Static texts and button title's are appropriate // 2. Static texts and button title's are appropriate
func testForwardButton() { func testForwardButton() {
welcomeControllerTests() welcomeControllerTests()
// Let's go to the Permissions View // Let's go to the Permissions View
moveForward() moveForward()
permissionsControllerTests() permissionsControllerTests()
// Time to test the launchAtLoginView // Time to test the launchAtLoginView
moveForward() moveForward()
startupControllerTests() startupControllerTests()
// Let's go to OnboardingSearchController // Let's go to OnboardingSearchController
moveForward() moveForward()
searchControllerTests() searchControllerTests()
// Let's go to the FinalOnboardingController // Let's go to the FinalOnboardingController
moveForward() moveForward()
finalOnboardingControllerTests() finalOnboardingControllerTests()
backButtonTests() backButtonTests()
} }
func backButtonTests() { func backButtonTests() {
moveBackward() moveBackward()
searchControllerTests() searchControllerTests()
moveBackward() moveBackward()
startupControllerTests() startupControllerTests()
moveBackward() moveBackward()
permissionsControllerTests() permissionsControllerTests()
moveBackward() moveBackward()
welcomeControllerTests() welcomeControllerTests()
alternateStartupFlowTests() alternateStartupFlowTests()
} }
func alternateStartupFlowTests() { func alternateStartupFlowTests() {
// Let's go to the Permissions View // Let's go to the Permissions View
moveForward() moveForward()
permissionsControllerTests() permissionsControllerTests()
// Time to test the launchAtLoginView // Time to test the launchAtLoginView
moveForward() moveForward()
startupControllerTests() startupControllerTests()
// Let's go to OnboardingSearchController // Let's go to OnboardingSearchController
alternateMoveForward() alternateMoveForward()
searchControllerTests() searchControllerTests()
// Let's go to the FinalOnboardingController // Let's go to the FinalOnboardingController
moveForward() moveForward()
finalOnboardingControllerTests() finalOnboardingControllerTests()
moveForward() moveForward()
XCTAssertTrue(app.statusItems.count > 0, "Status item was not installed in the menubar") XCTAssertTrue(app.statusItems.count > 0, "Status item was not installed in the menubar")
} }
private func moveForward() { private func moveForward() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Forward"].click() onboardingWindow.buttons["Forward"].click()
sleep(1) sleep(1)
} }
private func alternateMoveForward() { private func alternateMoveForward() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Alternate"].click() onboardingWindow.buttons["Alternate"].click()
sleep(1) sleep(1)
} }
private func moveBackward() { private func moveBackward() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Backward"].click() onboardingWindow.buttons["Backward"].click()
sleep(1) sleep(1)
} }
private func welcomeControllerTests() { private func welcomeControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
// Tests static texts // Tests static texts
XCTAssertTrue(onboardingWindow.staticTexts["Clocker"].exists, "Static text Clocker was unexpectedly missing") XCTAssertTrue(onboardingWindow.staticTexts["Clocker"].exists, "Static text Clocker was unexpectedly missing")
XCTAssertTrue(onboardingWindow.staticTexts["It only takes 3 steps to set up Clocker."].exists, "Accessory label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["It only takes 3 steps to set up Clocker."].exists, "Accessory label's static text was unexpectedly wrong.")
let button = onboardingWindow.buttons["Forward"] let button = onboardingWindow.buttons["Forward"]
// Test the button title // Test the button title
XCTAssertTrue(button.exists, "Button title was unexpectedly wrong. Expected \"Get Started\", Actual: \"\(onboardingWindow.buttons.firstMatch.title)\" ") XCTAssertTrue(button.exists, "Button title was unexpectedly wrong. Expected \"Get Started\", Actual: \"\(onboardingWindow.buttons.firstMatch.title)\" ")
XCTAssertTrue(onboardingWindow.buttons.count == 1, "More than 1 button on Welcome screen!") XCTAssertTrue(onboardingWindow.buttons.count == 1, "More than 1 button on Welcome screen!")
} }
private func permissionsControllerTests() { private func permissionsControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertTrue(onboardingWindow.staticTexts["Permissions"].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Permissions"].exists, "Header label's static text was unexpectedly wrong.")
XCTAssertTrue(onboardingWindow.staticTexts["These can be configured later in System Preferences."].exists, "Onboarding Info label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["These can be configured later in System Preferences."].exists, "Onboarding Info label's static text was unexpectedly wrong.")
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong")
XCTAssertTrue(onboardingWindow.buttons["Backward"].exists, "Back button was unexpectedly missing") XCTAssertTrue(onboardingWindow.buttons["Backward"].exists, "Back button was unexpectedly missing")
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
} }
private func startupControllerTests() { private func startupControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Open Clocker At Login", "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Open Clocker At Login", "Forward button title's was unexpectedly wrong")
XCTAssertTrue(onboardingWindow.buttons["Alternate"].title == "Don't Open", "Alternate button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Alternate"].title == "Don't Open", "Alternate button title's was unexpectedly wrong")
XCTAssertTrue(onboardingWindow.staticTexts["Launch at Login"].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Launch at Login"].exists, "Header label's static text was unexpectedly wrong.")
XCTAssertTrue(onboardingWindow.staticTexts["Should Clocker open automatically on startup?"].exists, "Accessory label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Should Clocker open automatically on startup?"].exists, "Accessory label's static text was unexpectedly wrong.")
} }
private func searchControllerTests() { private func searchControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong")
XCTAssertTrue(onboardingWindow.staticTexts["Quick Add Locations"].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Quick Add Locations"].exists, "Header label's static text was unexpectedly wrong.")
XCTAssertTrue(onboardingWindow.staticTexts["More search options in Clocker Preferences."].exists, "Accessory label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["More search options in Clocker Preferences."].exists, "Accessory label's static text was unexpectedly wrong.")
} }
private func finalOnboardingControllerTests() { private func finalOnboardingControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
// Let's test the buttons // Let's test the buttons
XCTAssertTrue(onboardingWindow.staticTexts["You're all set!"].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["You're all set!"].exists, "Header label's static text was unexpectedly wrong.")
XCTAssertTrue(onboardingWindow.staticTexts["Thank you for the details."].exists, "Accessory label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Thank you for the details."].exists, "Accessory label's static text was unexpectedly wrong.")
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Launch Clocker", "Forward button's title was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Launch Clocker", "Forward button's title was unexpectedly wrong.")
} }

10
Clocker/ClockerUITests/PanelTests.swift

@ -107,15 +107,15 @@ class PanelTests: XCTestCase {
XCTAssertNotEqual(afterUpcomingEventViewExists, beforeUpcomingEventViewExist) XCTAssertNotEqual(afterUpcomingEventViewExists, beforeUpcomingEventViewExist)
} }
func testRightMouseDownToShowPopover() { func testRightMouseDownToShowPopover() {
app.tapMenubarIcon() app.tapMenubarIcon()
let cell = app.tables["mainTableView"].cells.firstMatch let cell = app.tables["mainTableView"].cells.firstMatch
cell.rightClick() cell.rightClick()
XCTAssert(app.popovers.count > 0) XCTAssert(app.popovers.count > 0)
} }
} }

82
Clocker/ClockerUITests/PreferencesTest.swift

@ -54,36 +54,36 @@ class PreferencesTest: XCTestCase {
deleteAPlace(place: "UTC", for: app) deleteAPlace(place: "UTC", for: app)
} }
func testEditingLabel() { func testEditingLabel() {
let placeToAdd = "Auckland" let placeToAdd = "Auckland"
app.tapMenubarIcon() app.tapMenubarIcon()
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
if app.sheets.count == 0 { if app.sheets.count == 0 {
app.windows["Clocker"].checkBoxes["AddTimezone"].click() app.windows["Clocker"].checkBoxes["AddTimezone"].click()
} }
addAPlace(place: placeToAdd, to: app) addAPlace(place: placeToAdd, to: app)
let matchPredicate = NSPredicate(format: "value == %@", placeToAdd) let matchPredicate = NSPredicate(format: "value == %@", placeToAdd)
let matchingFields = app.windows["Clocker"].textFields.matching(matchPredicate) let matchingFields = app.windows["Clocker"].textFields.matching(matchPredicate)
XCTAssertTrue(matchingFields.count > 1, "Matching Fields count was zero") XCTAssertTrue(matchingFields.count > 1, "Matching Fields count was zero")
matchingFields.element(boundBy: 1).doubleClick() matchingFields.element(boundBy: 1).doubleClick()
matchingFields.element(boundBy: 1).typeText("NZ") matchingFields.element(boundBy: 1).typeText("NZ")
app.typeKey(XCUIKeyboardKey.return, modifierFlags: []) app.typeKey(XCUIKeyboardKey.return, modifierFlags: [])
app.tapMenubarIcon() app.tapMenubarIcon()
let labelPredicate = NSPredicate(format: "label == %@", "NZ") let labelPredicate = NSPredicate(format: "label == %@", "NZ")
let cells = app.tables["mainTableView"].cells.matching(labelPredicate) let cells = app.tables["mainTableView"].cells.matching(labelPredicate)
XCTAssert(cells.count > 0) XCTAssert(cells.count > 0)
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
deleteAPlace(place: placeToAdd, for: app) deleteAPlace(place: placeToAdd, for: app)
} }
func testSortingByTimezoneDifference() { func testSortingByTimezoneDifference() {
@ -129,7 +129,7 @@ class PreferencesTest: XCTestCase {
addAPlace(place: "Omaha", to: app) addAPlace(place: "Omaha", to: app)
addAPlace(place: "Mumbai", to: app) addAPlace(place: "Mumbai", to: app)
} }
func testSortingByTimezoneName() { func testSortingByTimezoneName() {
app.tapMenubarIcon() app.tapMenubarIcon()
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
@ -220,22 +220,22 @@ class PreferencesTest: XCTestCase {
func testSearchingWithMisspelledName() { func testSearchingWithMisspelledName() {
app.tapMenubarIcon() app.tapMenubarIcon()
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
if app.sheets.count == 0 { if app.sheets.count == 0 {
app.windows["Clocker"].checkBoxes["AddTimezone"].click() app.windows["Clocker"].checkBoxes["AddTimezone"].click()
} }
let searchField = app.searchFields["AvailableSearchField"] let searchField = app.searchFields["AvailableSearchField"]
searchField.reset(text: "StuJjlqh7AcJFnBuOdgNa2dQ4WrIajP9Mo8R83FV7fIZ3B8zE2n") searchField.reset(text: "StuJjlqh7AcJFnBuOdgNa2dQ4WrIajP9Mo8R83FV7fIZ3B8zE2n")
sleep(1) sleep(1)
let maxCharacterCountPredicate = NSPredicate(format: "value like %@", "Only 50 characters allowed!") let maxCharacterCountPredicate = NSPredicate(format: "value like %@", "Only 50 characters allowed!")
let currentSheets = app.sheets.firstMatch.staticTexts let currentSheets = app.sheets.firstMatch.staticTexts
let maxCharacterQuery = currentSheets.matching(maxCharacterCountPredicate) let maxCharacterQuery = currentSheets.matching(maxCharacterCountPredicate)
XCTAssertTrue(maxCharacterQuery.count > 0) XCTAssertTrue(maxCharacterQuery.count > 0)
addAPlace(place: "asdakjhdasdahsdasd", to: app, shouldSleep: false) addAPlace(place: "asdakjhdasdahsdasd", to: app, shouldSleep: false)
XCTAssertTrue(app.sheets.staticTexts["Please select a timezone!"].exists) XCTAssertTrue(app.sheets.staticTexts["Please select a timezone!"].exists)
@ -276,86 +276,86 @@ class PreferencesTest: XCTestCase {
XCTAssertTrue(newPlaceholder.exists, "Search Field doesn't exist") XCTAssertTrue(newPlaceholder.exists, "Search Field doesn't exist")
XCTAssertEqual(newPlaceholder.placeholderValue!, newPlaceholderValue) XCTAssertEqual(newPlaceholder.placeholderValue!, newPlaceholderValue)
} }
func testNoTimezone() { func testNoTimezone() {
app.tapMenubarIcon() app.tapMenubarIcon()
app.buttons["Preferences"].click() app.buttons["Preferences"].click()
deleteAllTimezones() deleteAllTimezones()
XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists)
XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists)
app.tapMenubarIcon() app.tapMenubarIcon()
XCTAssertTrue(app.buttons["EmptyAddTimezone"].exists) XCTAssertTrue(app.buttons["EmptyAddTimezone"].exists)
addAPlace(place: "Omaha", to: app) addAPlace(place: "Omaha", to: app)
addAPlace(place: "Mumbai", to: app) addAPlace(place: "Mumbai", to: app)
deleteAllTimezones() deleteAllTimezones()
XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneEmoji"].exists)
XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists) XCTAssertTrue(app.staticTexts["NoTimezoneMessage"].exists)
addAPlace(place: "Omaha", to: app) addAPlace(place: "Omaha", to: app)
addAPlace(place: "Mumbai", to: app) addAPlace(place: "Mumbai", to: app)
} }
func testWarningIfMoreThanOneMenubarIsSelected() { func testWarningIfMoreThanOneMenubarIsSelected() {
app.tapMenubarIcon() app.tapMenubarIcon()
app.buttons["Preferences"].click() app.buttons["Preferences"].click()
let preferencesTable = app.tables["TimezoneTableView"] let preferencesTable = app.tables["TimezoneTableView"]
XCTAssertTrue(preferencesTable.exists) XCTAssertTrue(preferencesTable.exists)
// Let's reset all checkboxes // Let's reset all checkboxes
let favouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 1", "")) let favouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 1", ""))
if favouritedMenubarsQuery.count > 1 { if favouritedMenubarsQuery.count > 1 {
for _ in 0..<favouritedMenubarsQuery.count { for _ in 0..<favouritedMenubarsQuery.count {
let checkbox = favouritedMenubarsQuery.element(boundBy: 0) let checkbox = favouritedMenubarsQuery.element(boundBy: 0)
checkbox.click() checkbox.click()
} }
} }
// Let's make sure we have > 1 timezones first // Let's make sure we have > 1 timezones first
let favourites = preferencesTable.tableRows let favourites = preferencesTable.tableRows
XCTAssertTrue(favourites.count > 1) XCTAssertTrue(favourites.count > 1)
// Select two timezones // Select two timezones
let unfavouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 0", "")) let unfavouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 0", ""))
if unfavouritedMenubarsQuery.count > 1 { if unfavouritedMenubarsQuery.count > 1 {
for _ in 0..<2{ for _ in 0..<2 {
let checkbox = unfavouritedMenubarsQuery.element(boundBy: 0) let checkbox = unfavouritedMenubarsQuery.element(boundBy: 0)
checkbox.click() checkbox.click()
} }
} }
XCTAssertTrue(app.dialogs.count > 0) XCTAssertTrue(app.dialogs.count > 0)
let compactModeButton = app.dialogs.buttons["Enable Compact Mode"] let compactModeButton = app.dialogs.buttons["Enable Compact Mode"]
if compactModeButton.isHittable { if compactModeButton.isHittable {
compactModeButton.click() compactModeButton.click()
XCTAssertTrue(app.dialogs.count == 0) XCTAssertTrue(app.dialogs.count == 0)
} }
} }
private func deleteAllTimezones() { private func deleteAllTimezones() {
let clockerWindow = app.windows["Clocker"] let clockerWindow = app.windows["Clocker"]
let rowQueryCount = clockerWindow.tables["TimezoneTableView"].tableRows.count let rowQueryCount = clockerWindow.tables["TimezoneTableView"].tableRows.count
if rowQueryCount > 0 { if rowQueryCount > 0 {
let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch
currentElement.click() currentElement.click()
for _ in 0 ..< rowQueryCount { for _ in 0 ..< rowQueryCount {
clockerWindow.typeKey(XCUIKeyboardKey.delete, clockerWindow.typeKey(XCUIKeyboardKey.delete,
modifierFlags: XCUIElement.KeyModifierFlags()) modifierFlags: XCUIElement.KeyModifierFlags())
} }
} }
} }
} }

14
Clocker/ClockerUITests/ReviewTests.swift

@ -3,23 +3,23 @@
import XCTest import XCTest
class ReviewTests: XCTestCase { class ReviewTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
super.setUp() super.setUp()
continueAfterFailure = false continueAfterFailure = false
app = XCUIApplication() app = XCUIApplication()
app.launchArguments.append(CLUITestingLaunchArgument) app.launchArguments.append(CLUITestingLaunchArgument)
app.launch() app.launch()
app.tapMenubarIcon() app.tapMenubarIcon()
app.tapMenubarIcon() app.tapMenubarIcon()
app.tapMenubarIcon() app.tapMenubarIcon()
} }
func testIfReviewIsNegativeAndUserWantsToProvideFeedback() { func testIfReviewIsNegativeAndUserWantsToProvideFeedback() {
guard app.buttons["Not Really"].exists else { return } guard app.buttons["Not Really"].exists else { return }
XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.staticTexts["ReviewLabel"].exists)
app.buttons["Not Really"].click() app.buttons["Not Really"].click()
@ -28,7 +28,7 @@ class ReviewTests: XCTestCase {
XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) XCTAssertFalse(app.staticTexts["ReviewLabel"].exists)
XCTAssertTrue(app.windows["Clocker Feedback"].exists) XCTAssertTrue(app.windows["Clocker Feedback"].exists)
} }
func testIfReviewIsNegativeAndNoFeedback() { func testIfReviewIsNegativeAndNoFeedback() {
guard app.buttons["Not Really"].exists else { return } guard app.buttons["Not Really"].exists else { return }
XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.staticTexts["ReviewLabel"].exists)
@ -37,7 +37,7 @@ class ReviewTests: XCTestCase {
app.buttons["No, thanks"].click() app.buttons["No, thanks"].click()
XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) XCTAssertFalse(app.staticTexts["ReviewLabel"].exists)
} }
func testOnPositiveReviewAndNoAction() { func testOnPositiveReviewAndNoAction() {
guard app.buttons["Yes!"].exists else { return } guard app.buttons["Yes!"].exists else { return }
XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.staticTexts["ReviewLabel"].exists)
@ -46,7 +46,7 @@ class ReviewTests: XCTestCase {
app.buttons["No, thanks"].click() app.buttons["No, thanks"].click()
XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) XCTAssertFalse(app.staticTexts["ReviewLabel"].exists)
} }
func testOnPositiveReviewAndAction() { func testOnPositiveReviewAndAction() {
guard app.buttons["Yes!"].exists else { return } guard app.buttons["Yes!"].exists else { return }
XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.staticTexts["ReviewLabel"].exists)

28
Clocker/ClockerUITests/ShortcutTests.swift

@ -3,47 +3,47 @@
import XCTest import XCTest
class ShortcutTests: XCTestCase { class ShortcutTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
let randomIndex = Int(arc4random_uniform(26)) let randomIndex = Int(arc4random_uniform(26))
override func setUp() { override func setUp() {
super.setUp() super.setUp()
continueAfterFailure = false continueAfterFailure = false
app = XCUIApplication() app = XCUIApplication()
app.launch() app.launch()
app.tapMenubarIcon() app.tapMenubarIcon()
if !app.tables["mainTableView"].exists { if !app.tables["mainTableView"].exists {
app.buttons["FloatingPin"].click() app.buttons["FloatingPin"].click()
app.tapMenubarIcon() app.tapMenubarIcon()
} }
} }
func testShortcuts() { func testShortcuts() {
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
XCTAssertFalse(app.tables["mainTableView"].exists) XCTAssertFalse(app.tables["mainTableView"].exists)
let randomAlphabet = randomLetter() let randomAlphabet = randomLetter()
app.windows["Clocker"].buttons["ShortcutControl"].click() app.windows["Clocker"].buttons["ShortcutControl"].click()
app.windows["Clocker"].buttons["ShortcutControl"].typeKey(randomAlphabet, modifierFlags: [.shift, .command]) app.windows["Clocker"].buttons["ShortcutControl"].typeKey(randomAlphabet, modifierFlags: [.shift, .command])
// Close the window to really test // Close the window to really test
app.windows["Clocker"].buttons["_XCUI:CloseWindow"].click() app.windows["Clocker"].buttons["_XCUI:CloseWindow"].click()
app.typeKey(randomAlphabet, modifierFlags: [.shift, .command]) app.typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertTrue(app.tables["mainTableView"].exists) XCTAssertTrue(app.tables["mainTableView"].exists)
app.terminate() app.terminate()
app.launch() app.launch()
app.typeKey(randomAlphabet, modifierFlags: [.shift, .command]) app.typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertTrue(app.tables["mainTableView"].exists) XCTAssertTrue(app.tables["mainTableView"].exists)
// Reset the shortcut // Reset the shortcut
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
app.windows["Clocker"].buttons["ShortcutControl"].click() app.windows["Clocker"].buttons["ShortcutControl"].click()
@ -51,7 +51,7 @@ class ShortcutTests: XCTestCase {
app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command]) app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertFalse(app.tables["mainTableView"].exists) XCTAssertFalse(app.tables["mainTableView"].exists)
} }
private func randomLetter() -> String { private func randomLetter() -> String {
let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
return alphabet[randomIndex] return alphabet[randomIndex]

104
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -4,7 +4,7 @@ import XCTest
@testable import Clocker @testable import Clocker
class ClockerUnitTests: XCTestCase { class ClockerUnitTests: XCTestCase {
private let california = ["customLabel" : "Test", private let california = ["customLabel" : "Test",
"formattedAddress" : "San Francisco", "formattedAddress" : "San Francisco",
"place_id" : "TestIdentifier", "place_id" : "TestIdentifier",
@ -12,7 +12,7 @@ class ClockerUnitTests: XCTestCase {
"nextUpdate" : "", "nextUpdate" : "",
"latitude" : "37.7749295", "latitude" : "37.7749295",
"longitude" : "-122.4194155"] "longitude" : "-122.4194155"]
private let mumbai = ["customLabel" : "Ghar", private let mumbai = ["customLabel" : "Ghar",
"formattedAddress" : "Mumbai", "formattedAddress" : "Mumbai",
"place_id" : "ChIJwe1EZjDG5zsRaYxkjY_tpF0", "place_id" : "ChIJwe1EZjDG5zsRaYxkjY_tpF0",
@ -20,7 +20,7 @@ class ClockerUnitTests: XCTestCase {
"nextUpdate" : "", "nextUpdate" : "",
"latitude" : "19.0759837", "latitude" : "19.0759837",
"longitude" : "72.8776559"] "longitude" : "72.8776559"]
private let auckland = ["customLabel" : "Auckland", private let auckland = ["customLabel" : "Auckland",
"formattedAddress" : "New Zealand", "formattedAddress" : "New Zealand",
"place_id" : "ChIJh5Z3Fw4gLG0RM0dqdeIY1rE", "place_id" : "ChIJh5Z3Fw4gLG0RM0dqdeIY1rE",
@ -28,7 +28,7 @@ class ClockerUnitTests: XCTestCase {
"nextUpdate" : "", "nextUpdate" : "",
"latitude" : "-40.900557", "latitude" : "-40.900557",
"longitude" : "174.885971"] "longitude" : "174.885971"]
private let florida = ["customLabel" : "Gainesville", private let florida = ["customLabel" : "Gainesville",
"formattedAddress" : "Florida", "formattedAddress" : "Florida",
"place_id" : "ChIJvypWkWV2wYgR0E7HW9MTLvc", "place_id" : "ChIJvypWkWV2wYgR0E7HW9MTLvc",
@ -36,37 +36,37 @@ class ClockerUnitTests: XCTestCase {
"nextUpdate" : "", "nextUpdate" : "",
"latitude" : "27.664827", "latitude" : "27.664827",
"longitude" : "-81.5157535"] "longitude" : "-81.5157535"]
private let onlyTimezone: [String: Any] = ["timezoneID": "Africa/Algiers", private let onlyTimezone: [String: Any] = ["timezoneID": "Africa/Algiers",
"formattedAddress" : "Africa/Algiers", "formattedAddress" : "Africa/Algiers",
"place_id": "", "place_id": "",
"customLabel": "", "customLabel": "",
"latitude": "", "latitude": "",
"longitude": ""] "longitude": ""]
private let omaha: [String: Any] = ["timezoneID": "America/Chicago", private let omaha: [String: Any] = ["timezoneID": "America/Chicago",
"formattedAddress" : "Omaha", "formattedAddress" : "Omaha",
"place_id": "ChIJ7fwMtciNk4cRBLY3rk9NQkY", "place_id": "ChIJ7fwMtciNk4cRBLY3rk9NQkY",
"customLabel": "", "customLabel": "",
"latitude": "41.2565369", "latitude": "41.2565369",
"longitude": "-95.9345034"] "longitude": "-95.9345034"]
private var operations: TimezoneDataOperations { private var operations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: mumbai)) return TimezoneDataOperations(with: TimezoneData(with: mumbai))
} }
private var californiaOperations: TimezoneDataOperations { private var californiaOperations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: california)) return TimezoneDataOperations(with: TimezoneData(with: california))
} }
private var floridaOperations: TimezoneDataOperations { private var floridaOperations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: florida)) return TimezoneDataOperations(with: TimezoneData(with: florida))
} }
private var aucklandOperations: TimezoneDataOperations { private var aucklandOperations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: auckland)) return TimezoneDataOperations(with: TimezoneData(with: auckland))
} }
private var omahaOperations: TimezoneDataOperations { private var omahaOperations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: omaha)) return TimezoneDataOperations(with: TimezoneData(with: omaha))
} }
@ -74,32 +74,32 @@ class ClockerUnitTests: XCTestCase {
func testAddingATimezoneToDefaults() { func testAddingATimezoneToDefaults() {
let timezoneData = TimezoneData(with: california) let timezoneData = TimezoneData(with: california)
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
let currentFavourites = (defaults.object(forKey: CLDefaultPreferenceKey) as? [Data]) ?? [] let currentFavourites = (defaults.object(forKey: CLDefaultPreferenceKey) as? [Data]) ?? []
let oldCount = currentFavourites.count let oldCount = currentFavourites.count
let operationsObject = TimezoneDataOperations(with: timezoneData) let operationsObject = TimezoneDataOperations(with: timezoneData)
operationsObject.saveObject() operationsObject.saveObject()
let newDefaults = UserDefaults.standard.object(forKey: CLDefaultPreferenceKey) as? [Data] let newDefaults = UserDefaults.standard.object(forKey: CLDefaultPreferenceKey) as? [Data]
XCTAssert(newDefaults != nil) XCTAssert(newDefaults != nil)
XCTAssert(newDefaults?.count == oldCount + 1) XCTAssert(newDefaults?.count == oldCount + 1)
} }
func testDeletingATimezone() { func testDeletingATimezone() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
var currentFavourites = defaults.object(forKey: CLDefaultPreferenceKey) as! [Data] var currentFavourites = defaults.object(forKey: CLDefaultPreferenceKey) as! [Data]
let oldCount = currentFavourites.count let oldCount = currentFavourites.count
currentFavourites = currentFavourites.filter { currentFavourites = currentFavourites.filter {
let timezone = TimezoneData.customObject(from: $0) let timezone = TimezoneData.customObject(from: $0)
return timezone?.placeID != "TestIdentifier" return timezone?.placeID != "TestIdentifier"
} }
defaults.set(currentFavourites, forKey: CLDefaultPreferenceKey) defaults.set(currentFavourites, forKey: CLDefaultPreferenceKey)
XCTAssertTrue(currentFavourites.count == oldCount - 1) XCTAssertTrue(currentFavourites.count == oldCount - 1)
} }
@ -111,108 +111,108 @@ class ClockerUnitTests: XCTestCase {
XCTAssertTrue(aucklandOperations.timeDifference() == ", 19 hours ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())") XCTAssertTrue(aucklandOperations.timeDifference() == ", 19 hours ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())")
XCTAssertTrue(omahaOperations.timeDifference() == ", 2 hours ahead", "Difference was unexpectedly: \(omahaOperations.timeDifference())") XCTAssertTrue(omahaOperations.timeDifference() == ", 2 hours ahead", "Difference was unexpectedly: \(omahaOperations.timeDifference())")
} }
func testSunriseSunset() { func testSunriseSunset() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let operations = TimezoneDataOperations(with: dataObject) let operations = TimezoneDataOperations(with: dataObject)
XCTAssertNotNil(operations.formattedSunriseTime(with: 0)) XCTAssertNotNil(operations.formattedSunriseTime(with: 0))
XCTAssertNotNil(dataObject.sunriseTime) XCTAssertNotNil(dataObject.sunriseTime)
XCTAssertNotNil(dataObject.sunriseTime) XCTAssertNotNil(dataObject.sunriseTime)
let timezoneObject = TimezoneData(with: onlyTimezone) let timezoneObject = TimezoneData(with: onlyTimezone)
timezoneObject.selectionType = .timezone timezoneObject.selectionType = .timezone
let timezoneOperations = TimezoneDataOperations(with: timezoneObject) let timezoneOperations = TimezoneDataOperations(with: timezoneObject)
XCTAssertTrue(timezoneOperations.formattedSunriseTime(with: 0) == "") XCTAssertTrue(timezoneOperations.formattedSunriseTime(with: 0) == "")
XCTAssertNil(timezoneObject.sunriseTime) XCTAssertNil(timezoneObject.sunriseTime)
XCTAssertNil(timezoneObject.sunsetTime) XCTAssertNil(timezoneObject.sunsetTime)
} }
func testDateWithSliderValue() { func testDateWithSliderValue() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let operations = TimezoneDataOperations(with: dataObject) let operations = TimezoneDataOperations(with: dataObject)
XCTAssertNotNil(operations.date(with: 0, displayType: .menuDisplay)) XCTAssertNotNil(operations.date(with: 0, displayType: .menuDisplay))
} }
func testTimezoneFormat() { func testTimezoneFormat() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
UserDefaults.standard.set(NSNumber(value: 0), forKey: CLShowSecondsInMenubar) // Set to show seconds UserDefaults.standard.set(NSNumber(value: 0), forKey: CLShowSecondsInMenubar) // Set to show seconds
UserDefaults.standard.set(NSNumber(value: 0), forKey: CL24hourFormatSelectedKey) // Set to 12 hour format UserDefaults.standard.set(NSNumber(value: 0), forKey: CL24hourFormatSelectedKey) // Set to 12 hour format
dataObject.setShouldOverrideGlobalTimeFormat(0) dataObject.setShouldOverrideGlobalTimeFormat(0)
XCTAssertTrue(dataObject.timezoneFormat() == "h:mm:ss a") XCTAssertTrue(dataObject.timezoneFormat() == "h:mm:ss a")
dataObject.setShouldOverrideGlobalTimeFormat(1) dataObject.setShouldOverrideGlobalTimeFormat(1)
XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm:ss") XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm:ss")
dataObject.setShouldOverrideGlobalTimeFormat(2) dataObject.setShouldOverrideGlobalTimeFormat(2)
XCTAssertTrue(dataObject.timezoneFormat() == "h:mm:ss a") XCTAssertTrue(dataObject.timezoneFormat() == "h:mm:ss a")
UserDefaults.standard.set(NSNumber(value: 1), forKey: CL24hourFormatSelectedKey) // Set to 24-Hour Format UserDefaults.standard.set(NSNumber(value: 1), forKey: CL24hourFormatSelectedKey) // Set to 24-Hour Format
XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm:ss") XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm:ss")
UserDefaults.standard.set(NSNumber(value: 1), forKey: CLShowSecondsInMenubar) UserDefaults.standard.set(NSNumber(value: 1), forKey: CLShowSecondsInMenubar)
dataObject.setShouldOverrideGlobalTimeFormat(0) dataObject.setShouldOverrideGlobalTimeFormat(0)
XCTAssertTrue(dataObject.timezoneFormat() == "h:mm a") XCTAssertTrue(dataObject.timezoneFormat() == "h:mm a")
dataObject.setShouldOverrideGlobalTimeFormat(1) dataObject.setShouldOverrideGlobalTimeFormat(1)
XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm") XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm")
dataObject.setShouldOverrideGlobalTimeFormat(2) dataObject.setShouldOverrideGlobalTimeFormat(2)
XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm") XCTAssertTrue(dataObject.timezoneFormat() == "HH:mm")
UserDefaults.standard.set(NSNumber(value: 0), forKey: CL24hourFormatSelectedKey) UserDefaults.standard.set(NSNumber(value: 0), forKey: CL24hourFormatSelectedKey)
XCTAssertTrue(dataObject.timezoneFormat() == "h:mm a") XCTAssertTrue(dataObject.timezoneFormat() == "h:mm a")
} }
func testFormattedLabel() { func testFormattedLabel() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model.") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model.")
dataObject.customLabel = nil dataObject.customLabel = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Mumbai", "Incorrect custom label returned by model.") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Mumbai", "Incorrect custom label returned by model.")
dataObject.formattedAddress = nil dataObject.formattedAddress = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model.") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model.")
} }
func testEquality() { func testEquality() {
let dataObject1 = TimezoneData(with: mumbai) let dataObject1 = TimezoneData(with: mumbai)
let dataObject2 = TimezoneData(with: auckland) let dataObject2 = TimezoneData(with: auckland)
XCTAssertFalse(dataObject1 == dataObject2) XCTAssertFalse(dataObject1 == dataObject2)
XCTAssertFalse(dataObject1.isEqual(dataObject2)) XCTAssertFalse(dataObject1.isEqual(dataObject2))
let dataObject3 = TimezoneData(with: mumbai) let dataObject3 = TimezoneData(with: mumbai)
XCTAssertTrue(dataObject1 == dataObject3) XCTAssertTrue(dataObject1 == dataObject3)
XCTAssertTrue(dataObject1.isEqual(dataObject3)) XCTAssertTrue(dataObject1.isEqual(dataObject3))
XCTAssertFalse(dataObject1.isEqual(nil)) XCTAssertFalse(dataObject1.isEqual(nil))
} }
func testWithAllLocales() { func testWithAllLocales() {
let dataObject1 = TimezoneData(with: mumbai) let dataObject1 = TimezoneData(with: mumbai)
let operations = TimezoneDataOperations(with: dataObject1) let operations = TimezoneDataOperations(with: dataObject1)
for locale in Locale.availableIdentifiers { for locale in Locale.availableIdentifiers {
let currentLocale = Locale(identifier: locale) let currentLocale = Locale(identifier: locale)
let localizedDate = operations.todaysDate(with: 0, locale: currentLocale) let localizedDate = operations.todaysDate(with: 0, locale: currentLocale)
XCTAssertNotNil(localizedDate) XCTAssertNotNil(localizedDate)
} }
} }
func testTimeWithAllLocales() { func testTimeWithAllLocales() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let cal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) let cal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
guard let newDate = cal?.date(byAdding: .minute, guard let newDate = cal?.date(byAdding: .minute,
value: 0, value: 0,
to: Date(), to: Date(),
@ -220,7 +220,7 @@ class ClockerUnitTests: XCTestCase {
XCTFail("Unable to add dates!") XCTFail("Unable to add dates!")
return return
} }
for locale in Locale.availableIdentifiers { for locale in Locale.availableIdentifiers {
let currentLocale = Locale(identifier: locale) let currentLocale = Locale(identifier: locale)
let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none, let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none,

2
Clocker/ClockerUnitTests/RateTests.swift

@ -4,7 +4,7 @@ import XCTest
@testable import Clocker @testable import Clocker
class RateTests: XCTestCase { class RateTests: XCTestCase {
let rateController = RateController.applicationDidLaunch(UserDefaults()) let rateController = RateController.applicationDidLaunch(UserDefaults())
override func setUp() { override func setUp() {

3
Clocker/Dependencies/Date Additions/Constants.swift

@ -6,7 +6,6 @@
// Copyright © 2016 Grayson Webster. All rights reserved. // Copyright © 2016 Grayson Webster. All rights reserved.
// //
import Foundation import Foundation
/** /**
@ -24,6 +23,6 @@ public class Constants {
public static let SecondsInHour: TimeInterval = 3600 public static let SecondsInHour: TimeInterval = 3600
public static let SecondsInMinute: TimeInterval = 60 public static let SecondsInMinute: TimeInterval = 60
public static let MillisecondsInDay: TimeInterval = 86400000 public static let MillisecondsInDay: TimeInterval = 86400000
public static let AllCalendarUnitFlags: Set<Calendar.Component> = [.year, .quarter, .month, .weekOfYear, .weekOfMonth, .day, .hour, .minute, .second, .era, .weekday, .weekdayOrdinal, .weekOfYear] public static let AllCalendarUnitFlags: Set<Calendar.Component> = [.year, .quarter, .month, .weekOfYear, .weekOfMonth, .day, .hour, .minute, .second, .era, .weekday, .weekdayOrdinal, .weekOfYear]
} }

3
Clocker/Dependencies/Date Additions/Date+Bundle.swift

@ -9,10 +9,9 @@
import Foundation import Foundation
public extension Bundle { public extension Bundle {
class func dateToolsBundle() -> Bundle { class func dateToolsBundle() -> Bundle {
let assetPath = Bundle(for: Constants.self).resourcePath! let assetPath = Bundle(for: Constants.self).resourcePath!
return Bundle(path: NSString(string: assetPath).appendingPathComponent("DateTools.bundle"))! return Bundle(path: NSString(string: assetPath).appendingPathComponent("DateTools.bundle"))!
} }
} }

162
Clocker/Dependencies/Date Additions/Date+Comparators.swift

@ -15,9 +15,9 @@ import Foundation
* for a given unit of time. * for a given unit of time.
*/ */
public extension Date { public extension Date {
// MARK: - Comparisons // MARK: - Comparisons
/** /**
* Given a date, returns a `TimeChunk` with components in their most natural form. Example: * Given a date, returns a `TimeChunk` with components in their most natural form. Example:
* *
@ -48,7 +48,7 @@ public extension Date {
return TimeChunk(seconds: compenentsBetween.second!, minutes: compenentsBetween.minute!, hours: compenentsBetween.hour!, days: compenentsBetween.day!, weeks: 0, months: compenentsBetween.month!, years: compenentsBetween.year!) return TimeChunk(seconds: compenentsBetween.second!, minutes: compenentsBetween.minute!, hours: compenentsBetween.hour!, days: compenentsBetween.day!, weeks: 0, months: compenentsBetween.month!, years: compenentsBetween.year!)
// TimeChunk(seconds: secondDelta, minutes: minuteDelta, hours: hourDelta, days: dayDelta, weeks: 0, months: monthDelta, years: yearDelta) // TimeChunk(seconds: secondDelta, minutes: minuteDelta, hours: hourDelta, days: dayDelta, weeks: 0, months: monthDelta, years: yearDelta)
} }
/** /**
* Returns a true if receiver is equal to provided comparison date, otherwise returns false * Returns a true if receiver is equal to provided comparison date, otherwise returns false
* *
@ -59,7 +59,7 @@ public extension Date {
func equals(_ date: Date) -> Bool { func equals(_ date: Date) -> Bool {
return self.compare(date) == .orderedSame return self.compare(date) == .orderedSame
} }
/** /**
* Returns a true if receiver is later than provided comparison date, otherwise * Returns a true if receiver is later than provided comparison date, otherwise
* returns false * returns false
@ -71,7 +71,7 @@ public extension Date {
func isLater(than date: Date) -> Bool { func isLater(than date: Date) -> Bool {
return self.compare(date) == .orderedDescending return self.compare(date) == .orderedDescending
} }
/** /**
* Returns a true if receiver is later than or equal to provided comparison date, * Returns a true if receiver is later than or equal to provided comparison date,
* otherwise returns false * otherwise returns false
@ -83,7 +83,7 @@ public extension Date {
func isLaterThanOrEqual(to date: Date) -> Bool { func isLaterThanOrEqual(to date: Date) -> Bool {
return self.compare(date) == .orderedDescending || self.compare(date) == .orderedSame return self.compare(date) == .orderedDescending || self.compare(date) == .orderedSame
} }
/** /**
* Returns a true if receiver is earlier than provided comparison date, otherwise * Returns a true if receiver is earlier than provided comparison date, otherwise
* returns false * returns false
@ -95,7 +95,7 @@ public extension Date {
func isEarlier(than date: Date) -> Bool { func isEarlier(than date: Date) -> Bool {
return self.compare(date) == .orderedAscending return self.compare(date) == .orderedAscending
} }
/** /**
* Returns a true if receiver is earlier than or equal to the provided comparison date, * Returns a true if receiver is earlier than or equal to the provided comparison date,
* otherwise returns false * otherwise returns false
@ -107,7 +107,7 @@ public extension Date {
func isEarlierThanOrEqual(to date: Date) -> Bool { func isEarlierThanOrEqual(to date: Date) -> Bool {
return self.compare(date) == .orderedAscending || self.compare(date) == .orderedSame return self.compare(date) == .orderedAscending || self.compare(date) == .orderedSame
} }
/** /**
* Returns whether two dates fall on the same day. * Returns whether two dates fall on the same day.
* *
@ -118,7 +118,7 @@ public extension Date {
func isSameDay(date : Date ) -> Bool { func isSameDay(date : Date ) -> Bool {
return Date.isSameDay(date: self, as: date) return Date.isSameDay(date: self, as: date)
} }
/** /**
* Returns whether two dates fall on the same day. * Returns whether two dates fall on the same day.
* *
@ -131,18 +131,17 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
var components = calendar.dateComponents([.era, .year, .month, .day], from: date) var components = calendar.dateComponents([.era, .year, .month, .day], from: date)
let dateOne = calendar.date(from: components) let dateOne = calendar.date(from: components)
components = calendar.dateComponents([.era, .year, .month, .day], from: compareDate) components = calendar.dateComponents([.era, .year, .month, .day], from: compareDate)
let dateTwo = calendar.date(from: components) let dateTwo = calendar.date(from: components)
return (dateOne?.equals(dateTwo!))! return (dateOne?.equals(dateTwo!))!
} }
// MARK: - Date Comparison // MARK: - Date Comparison
// MARK: Time From // MARK: Time From
/** /**
* Returns an Int representing the amount of time in years between the receiver and * Returns an Int representing the amount of time in years between the receiver and
* the provided date. * the provided date.
@ -157,7 +156,7 @@ public extension Date {
func years(from date: Date) -> Int { func years(from date: Date) -> Int {
return years(from: date, calendar:nil) return years(from: date, calendar:nil)
} }
/** /**
* Returns an Int representing the amount of time in months between the receiver and * Returns an Int representing the amount of time in months between the receiver and
* the provided date. * the provided date.
@ -172,7 +171,7 @@ public extension Date {
func months(from date: Date) -> Int { func months(from date: Date) -> Int {
return months(from: date, calendar:nil) return months(from: date, calendar:nil)
} }
/** /**
* Returns an Int representing the amount of time in weeks between the receiver and * Returns an Int representing the amount of time in weeks between the receiver and
* the provided date. * the provided date.
@ -187,7 +186,7 @@ public extension Date {
func weeks(from date: Date) -> Int { func weeks(from date: Date) -> Int {
return weeks(from: date, calendar:nil) return weeks(from: date, calendar:nil)
} }
/** /**
* Returns an Int representing the amount of time in days between the receiver and * Returns an Int representing the amount of time in days between the receiver and
* the provided date. * the provided date.
@ -202,7 +201,7 @@ public extension Date {
func days(from date: Date) -> Int { func days(from date: Date) -> Int {
return days(from: date, calendar:nil) return days(from: date, calendar:nil)
} }
/** /**
* Returns an Int representing the amount of time in hours between the receiver and * Returns an Int representing the amount of time in hours between the receiver and
* the provided date. * the provided date.
@ -214,9 +213,9 @@ public extension Date {
* - returns: The hours between receiver and provided date * - returns: The hours between receiver and provided date
*/ */
func hours(from date: Date) -> Int { func hours(from date: Date) -> Int {
return Int(self.timeIntervalSince(date)/Constants.SecondsInHour); return Int(self.timeIntervalSince(date)/Constants.SecondsInHour)
} }
/** /**
* Returns an Int representing the amount of time in minutes between the receiver and * Returns an Int representing the amount of time in minutes between the receiver and
* the provided date. * the provided date.
@ -230,7 +229,7 @@ public extension Date {
func minutes(from date: Date) -> Int { func minutes(from date: Date) -> Int {
return Int(self.timeIntervalSince(date)/Constants.SecondsInMinute) return Int(self.timeIntervalSince(date)/Constants.SecondsInMinute)
} }
/** /**
* Returns an Int representing the amount of time in seconds between the receiver and * Returns an Int representing the amount of time in seconds between the receiver and
* the provided date. * the provided date.
@ -244,10 +243,9 @@ public extension Date {
func seconds(from date: Date) -> Int { func seconds(from date: Date) -> Int {
return Int(timeIntervalSince(date)) return Int(timeIntervalSince(date))
} }
// MARK: Time From With Calendar // MARK: Time From With Calendar
/** /**
* Returns an Int representing the amount of time in years between the receiver and * Returns an Int representing the amount of time in years between the receiver and
* the provided date. * the provided date.
@ -264,14 +262,14 @@ public extension Date {
if (calendar == nil) { if (calendar == nil) {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
let earliest = earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self; let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1; let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents([.year], from: earliest, to: latest) let components = calendarCopy!.dateComponents([.year], from: earliest, to: latest)
return multiplier * components.year!; return multiplier * components.year!
} }
/** /**
* Returns an Int representing the amount of time in months between the receiver and * Returns an Int representing the amount of time in months between the receiver and
* the provided date. * the provided date.
@ -283,19 +281,19 @@ public extension Date {
* *
* - returns: The months between receiver and provided date * - returns: The months between receiver and provided date
*/ */
func months(from date: Date, calendar: Calendar?) -> Int{ func months(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if (calendar == nil) {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
let earliest = earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self; let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1; let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest) let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest)
return multiplier*(components.month! + 12*components.year!); return multiplier*(components.month! + 12*components.year!)
} }
/** /**
* Returns an Int representing the amount of time in weeks between the receiver and * Returns an Int representing the amount of time in weeks between the receiver and
* the provided date. * the provided date.
@ -307,19 +305,19 @@ public extension Date {
* *
* - returns: The weeks between receiver and provided date * - returns: The weeks between receiver and provided date
*/ */
func weeks(from date: Date, calendar: Calendar?) -> Int{ func weeks(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if (calendar == nil) {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
let earliest = earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self; let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1; let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest) let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest)
return multiplier*components.weekOfYear!; return multiplier*components.weekOfYear!
} }
/** /**
* Returns an Int representing the amount of time in days between the receiver and * Returns an Int representing the amount of time in days between the receiver and
* the provided date. * the provided date.
@ -336,17 +334,16 @@ public extension Date {
if (calendar == nil) { if (calendar == nil) {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
let earliest = earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1 let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest) let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest)
return multiplier*components.day! return multiplier*components.day!
} }
// MARK: Time Until // MARK: Time Until
/** /**
* The number of years until the receiver's date (0 if the receiver is the same or * The number of years until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
@ -354,7 +351,7 @@ public extension Date {
var yearsUntil: Int { var yearsUntil: Int {
return yearsLater(than: Date()) return yearsLater(than: Date())
} }
/** /**
* The number of months until the receiver's date (0 if the receiver is the same or * The number of months until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
@ -362,7 +359,7 @@ public extension Date {
var monthsUntil: Int { var monthsUntil: Int {
return monthsLater(than: Date()) return monthsLater(than: Date())
} }
/** /**
* The number of weeks until the receiver's date (0 if the receiver is the same or * The number of weeks until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
@ -370,7 +367,7 @@ public extension Date {
var weeksUntil: Int { var weeksUntil: Int {
return weeksLater(than: Date()) return weeksLater(than: Date())
} }
/** /**
* The number of days until the receiver's date (0 if the receiver is the same or * The number of days until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
@ -378,34 +375,33 @@ public extension Date {
var daysUntil: Int { var daysUntil: Int {
return daysLater(than: Date()) return daysLater(than: Date())
} }
/** /**
* The number of hours until the receiver's date (0 if the receiver is the same or * The number of hours until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var hoursUntil: Int{ var hoursUntil: Int {
return hoursLater(than: Date()) return hoursLater(than: Date())
} }
/** /**
* The number of minutes until the receiver's date (0 if the receiver is the same or * The number of minutes until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var minutesUntil: Int{ var minutesUntil: Int {
return minutesLater(than: Date()) return minutesLater(than: Date())
} }
/** /**
* The number of seconds until the receiver's date (0 if the receiver is the same or * The number of seconds until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var secondsUntil: Int{ var secondsUntil: Int {
return secondsLater(than: Date()) return secondsLater(than: Date())
} }
// MARK: Time Ago // MARK: Time Ago
/** /**
* The number of years the receiver's date is earlier than now (0 if the receiver is * The number of years the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -413,7 +409,7 @@ public extension Date {
var yearsAgo: Int { var yearsAgo: Int {
return yearsEarlier(than: Date()) return yearsEarlier(than: Date())
} }
/** /**
* The number of months the receiver's date is earlier than now (0 if the receiver is * The number of months the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -421,7 +417,7 @@ public extension Date {
var monthsAgo: Int { var monthsAgo: Int {
return monthsEarlier(than: Date()) return monthsEarlier(than: Date())
} }
/** /**
* The number of weeks the receiver's date is earlier than now (0 if the receiver is * The number of weeks the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -429,7 +425,7 @@ public extension Date {
var weeksAgo: Int { var weeksAgo: Int {
return weeksEarlier(than: Date()) return weeksEarlier(than: Date())
} }
/** /**
* The number of days the receiver's date is earlier than now (0 if the receiver is * The number of days the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -437,7 +433,7 @@ public extension Date {
var daysAgo: Int { var daysAgo: Int {
return daysEarlier(than: Date()) return daysEarlier(than: Date())
} }
/** /**
* The number of hours the receiver's date is earlier than now (0 if the receiver is * The number of hours the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -445,7 +441,7 @@ public extension Date {
var hoursAgo: Int { var hoursAgo: Int {
return hoursEarlier(than: Date()) return hoursEarlier(than: Date())
} }
/** /**
* The number of minutes the receiver's date is earlier than now (0 if the receiver is * The number of minutes the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
@ -453,18 +449,17 @@ public extension Date {
var minutesAgo: Int { var minutesAgo: Int {
return minutesEarlier(than: Date()) return minutesEarlier(than: Date())
} }
/** /**
* The number of seconds the receiver's date is earlier than now (0 if the receiver is * The number of seconds the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var secondsAgo: Int{ var secondsAgo: Int {
return secondsEarlier(than: Date()) return secondsEarlier(than: Date())
} }
// MARK: Earlier Than // MARK: Earlier Than
/** /**
* Returns the number of years the receiver's date is earlier than the provided * Returns the number of years the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -476,7 +471,7 @@ public extension Date {
func yearsEarlier(than date: Date) -> Int { func yearsEarlier(than date: Date) -> Int {
return abs(min(years(from: date), 0)) return abs(min(years(from: date), 0))
} }
/** /**
* Returns the number of months the receiver's date is earlier than the provided * Returns the number of months the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -486,9 +481,9 @@ public extension Date {
* - returns: The number of months * - returns: The number of months
*/ */
func monthsEarlier(than date: Date) -> Int { func monthsEarlier(than date: Date) -> Int {
return abs(min(months(from: date), 0)); return abs(min(months(from: date), 0))
} }
/** /**
* Returns the number of weeks the receiver's date is earlier than the provided * Returns the number of weeks the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -500,7 +495,7 @@ public extension Date {
func weeksEarlier(than date: Date) -> Int { func weeksEarlier(than date: Date) -> Int {
return abs(min(weeks(from: date), 0)) return abs(min(weeks(from: date), 0))
} }
/** /**
* Returns the number of days the receiver's date is earlier than the provided * Returns the number of days the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -512,7 +507,7 @@ public extension Date {
func daysEarlier(than date: Date) -> Int { func daysEarlier(than date: Date) -> Int {
return abs(min(days(from: date), 0)) return abs(min(days(from: date), 0))
} }
/** /**
* Returns the number of hours the receiver's date is earlier than the provided * Returns the number of hours the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -524,7 +519,7 @@ public extension Date {
func hoursEarlier(than date: Date) -> Int { func hoursEarlier(than date: Date) -> Int {
return abs(min(hours(from: date), 0)) return abs(min(hours(from: date), 0))
} }
/** /**
* Returns the number of minutes the receiver's date is earlier than the provided * Returns the number of minutes the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -536,7 +531,7 @@ public extension Date {
func minutesEarlier(than date: Date) -> Int { func minutesEarlier(than date: Date) -> Int {
return abs(min(minutes(from: date), 0)) return abs(min(minutes(from: date), 0))
} }
/** /**
* Returns the number of seconds the receiver's date is earlier than the provided * Returns the number of seconds the receiver's date is earlier than the provided
* comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date.
@ -548,10 +543,9 @@ public extension Date {
func secondsEarlier(than date: Date) -> Int { func secondsEarlier(than date: Date) -> Int {
return abs(min(seconds(from: date), 0)) return abs(min(seconds(from: date), 0))
} }
// MARK: Later Than // MARK: Later Than
/** /**
* Returns the number of years the receiver's date is later than the provided * Returns the number of years the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -564,7 +558,7 @@ public extension Date {
func yearsLater(than date: Date) -> Int { func yearsLater(than date: Date) -> Int {
return max(years(from: date), 0) return max(years(from: date), 0)
} }
/** /**
* Returns the number of months the receiver's date is later than the provided * Returns the number of months the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -577,7 +571,7 @@ public extension Date {
func monthsLater(than date: Date) -> Int { func monthsLater(than date: Date) -> Int {
return max(months(from: date), 0) return max(months(from: date), 0)
} }
/** /**
* Returns the number of weeks the receiver's date is later than the provided * Returns the number of weeks the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -590,7 +584,7 @@ public extension Date {
func weeksLater(than date: Date) -> Int { func weeksLater(than date: Date) -> Int {
return max(weeks(from: date), 0) return max(weeks(from: date), 0)
} }
/** /**
* Returns the number of days the receiver's date is later than the provided * Returns the number of days the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -603,7 +597,7 @@ public extension Date {
func daysLater(than date: Date) -> Int { func daysLater(than date: Date) -> Int {
return max(days(from: date), 0) return max(days(from: date), 0)
} }
/** /**
* Returns the number of hours the receiver's date is later than the provided * Returns the number of hours the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -616,7 +610,7 @@ public extension Date {
func hoursLater(than date: Date) -> Int { func hoursLater(than date: Date) -> Int {
return max(hours(from: date), 0) return max(hours(from: date), 0)
} }
/** /**
* Returns the number of minutes the receiver's date is later than the provided * Returns the number of minutes the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
@ -629,7 +623,7 @@ public extension Date {
func minutesLater(than date: Date) -> Int { func minutesLater(than date: Date) -> Int {
return max(minutes(from: date), 0) return max(minutes(from: date), 0)
} }
/** /**
* Returns the number of seconds the receiver's date is later than the provided * Returns the number of seconds the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided

71
Clocker/Dependencies/Date Additions/Date+Components.swift

@ -27,7 +27,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.component(component, from: self) return calendar.component(component, from: self)
} }
/** /**
* Convenient accessor of the date's `Calendar` components ordinality. * Convenient accessor of the date's `Calendar` components ordinality.
* *
@ -41,7 +41,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.ordinality(of: smaller, in: larger, for: self) return calendar.ordinality(of: smaller, in: larger, for: self)
} }
/** /**
* Use calendar components to determine how many units of a smaller component are inside 1 larger unit. * Use calendar components to determine how many units of a smaller component are inside 1 larger unit.
* *
@ -60,10 +60,10 @@ public extension Date {
var unitRange: Range<Int>? var unitRange: Range<Int>?
if larger.hashValue < smaller.hashValue { if larger.hashValue < smaller.hashValue {
for x in larger.hashValue..<smaller.hashValue { for x in larger.hashValue..<smaller.hashValue {
var stepLarger: Calendar.Component var stepLarger: Calendar.Component
var stepSmaller: Calendar.Component var stepSmaller: Calendar.Component
switch(x) { switch(x) {
case 0: case 0:
stepLarger = Calendar.Component.era stepLarger = Calendar.Component.era
@ -110,7 +110,7 @@ public extension Date {
default: default:
return nil return nil
} }
if unitRange?.count != nil { if unitRange?.count != nil {
units *= (unitRange?.count)! units *= (unitRange?.count)!
} }
@ -119,107 +119,107 @@ public extension Date {
} }
return nil return nil
} }
// MARK: - Components // MARK: - Components
/** /**
* Convenience getter for the date's `era` component * Convenience getter for the date's `era` component
*/ */
var era: Int { var era: Int {
return component(.era) return component(.era)
} }
/** /**
* Convenience getter for the date's `year` component * Convenience getter for the date's `year` component
*/ */
var year: Int { var year: Int {
return component(.year) return component(.year)
} }
/** /**
* Convenience getter for the date's `month` component * Convenience getter for the date's `month` component
*/ */
var month: Int { var month: Int {
return component(.month) return component(.month)
} }
/** /**
* Convenience getter for the date's `week` component * Convenience getter for the date's `week` component
*/ */
var week: Int { var week: Int {
return component(.weekday) return component(.weekday)
} }
/** /**
* Convenience getter for the date's `day` component * Convenience getter for the date's `day` component
*/ */
var day: Int { var day: Int {
return component(.day) return component(.day)
} }
/** /**
* Convenience getter for the date's `hour` component * Convenience getter for the date's `hour` component
*/ */
var hour: Int { var hour: Int {
return component(.hour) return component(.hour)
} }
/** /**
* Convenience getter for the date's `minute` component * Convenience getter for the date's `minute` component
*/ */
var minute: Int { var minute: Int {
return component(.minute) return component(.minute)
} }
/** /**
* Convenience getter for the date's `second` component * Convenience getter for the date's `second` component
*/ */
var second: Int { var second: Int {
return component(.second) return component(.second)
} }
/** /**
* Convenience getter for the date's `weekday` component * Convenience getter for the date's `weekday` component
*/ */
var weekday: Int { var weekday: Int {
return component(.weekday) return component(.weekday)
} }
/** /**
* Convenience getter for the date's `weekdayOrdinal` component * Convenience getter for the date's `weekdayOrdinal` component
*/ */
var weekdayOrdinal: Int { var weekdayOrdinal: Int {
return component(.weekdayOrdinal) return component(.weekdayOrdinal)
} }
/** /**
* Convenience getter for the date's `quarter` component * Convenience getter for the date's `quarter` component
*/ */
var quarter: Int { var quarter: Int {
return component(.quarter) return component(.quarter)
} }
/** /**
* Convenience getter for the date's `weekOfYear` component * Convenience getter for the date's `weekOfYear` component
*/ */
var weekOfMonth: Int { var weekOfMonth: Int {
return component(.weekOfMonth) return component(.weekOfMonth)
} }
/** /**
* Convenience getter for the date's `weekOfYear` component * Convenience getter for the date's `weekOfYear` component
*/ */
var weekOfYear: Int { var weekOfYear: Int {
return component(.weekOfYear) return component(.weekOfYear)
} }
/** /**
* Convenience getter for the date's `yearForWeekOfYear` component * Convenience getter for the date's `yearForWeekOfYear` component
*/ */
var yearForWeekOfYear: Int { var yearForWeekOfYear: Int {
return component(.yearForWeekOfYear) return component(.yearForWeekOfYear)
} }
/** /**
* Convenience getter for the date's `daysInMonth` component * Convenience getter for the date's `daysInMonth` component
*/ */
@ -228,60 +228,59 @@ public extension Date {
let days = calendar.range(of: .day, in: .month, for: self) let days = calendar.range(of: .day, in: .month, for: self)
return days!.count return days!.count
} }
// MARK: - Set Components // MARK: - Set Components
/** /**
* Convenience setter for the date's `year` component * Convenience setter for the date's `year` component
*/ */
mutating func year(_ year: Int) { mutating func year(_ year: Int) {
self = Date.init(year: year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: self.second) self = Date.init(year: year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: self.second)
} }
/** /**
* Convenience setter for the date's `month` component * Convenience setter for the date's `month` component
*/ */
mutating func month(_ month: Int) { mutating func month(_ month: Int) {
self = Date.init(year: self.year, month: month, day: self.day, hour: self.hour, minute: self.minute, second: self.second) self = Date.init(year: self.year, month: month, day: self.day, hour: self.hour, minute: self.minute, second: self.second)
} }
/** /**
* Convenience setter for the date's `day` component * Convenience setter for the date's `day` component
*/ */
mutating func day(_ day: Int) { mutating func day(_ day: Int) {
self = Date.init(year: self.year, month: self.month, day: day, hour: self.hour, minute: self.minute, second: self.second) self = Date.init(year: self.year, month: self.month, day: day, hour: self.hour, minute: self.minute, second: self.second)
} }
/** /**
* Convenience setter for the date's `hour` component * Convenience setter for the date's `hour` component
*/ */
mutating func hour(_ hour: Int) { mutating func hour(_ hour: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: hour, minute: self.minute, second: self.second) self = Date.init(year: self.year, month: self.month, day: self.day, hour: hour, minute: self.minute, second: self.second)
} }
/** /**
* Convenience setter for the date's `minute` component * Convenience setter for the date's `minute` component
*/ */
mutating func minute(_ minute: Int) { mutating func minute(_ minute: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: minute, second: self.second) self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: minute, second: self.second)
} }
/** /**
* Convenience setter for the date's `second` component * Convenience setter for the date's `second` component
*/ */
mutating func second(_ second: Int) { mutating func second(_ second: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: second) self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: second)
} }
// MARK: - Bools // MARK: - Bools
/** /**
* Determine if date is in a leap year * Determine if date is in a leap year
*/ */
var isInLeapYear: Bool { var isInLeapYear: Bool {
let yearComponent = component(.year) let yearComponent = component(.year)
if yearComponent % 400 == 0 { if yearComponent % 400 == 0 {
return true return true
} }
@ -293,7 +292,7 @@ public extension Date {
} }
return false return false
} }
/** /**
* Determine if date is within the current day * Determine if date is within the current day
*/ */
@ -301,7 +300,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInToday(self) return calendar.isDateInToday(self)
} }
/** /**
* Determine if date is within the day tomorrow * Determine if date is within the day tomorrow
*/ */
@ -309,7 +308,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInTomorrow(self) return calendar.isDateInTomorrow(self)
} }
/** /**
* Determine if date is within yesterday * Determine if date is within yesterday
*/ */
@ -317,7 +316,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInYesterday(self) return calendar.isDateInYesterday(self)
} }
/** /**
* Determine if date is in a weekend * Determine if date is in a weekend
*/ */

25
Clocker/Dependencies/Date Additions/Date+Format.swift

@ -12,9 +12,9 @@ import Foundation
* Extends the Date class by adding convenience methods for formatting dates. * Extends the Date class by adding convenience methods for formatting dates.
*/ */
public extension Date { public extension Date {
// MARK: - Formatted Date - Style // MARK: - Formatted Date - Style
/** /**
* Get string representation of date. * Get string representation of date.
* *
@ -29,10 +29,10 @@ public extension Date {
dateFormatter.dateStyle = dateStyle dateFormatter.dateStyle = dateStyle
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
dateFormatter.locale = locale dateFormatter.locale = locale
return dateFormatter.string(from: self) return dateFormatter.string(from: self)
} }
/** /**
* Get string representation of date. Locale is automatically selected as the current locale of the system. * Get string representation of date. Locale is automatically selected as the current locale of the system.
* *
@ -48,7 +48,7 @@ public extension Date {
return format(with: dateStyle, timeZone: timeZone, locale: Locale.autoupdatingCurrent) return format(with: dateStyle, timeZone: timeZone, locale: Locale.autoupdatingCurrent)
#endif #endif
} }
/** /**
* Get string representation of date. * Get string representation of date.
* Time zone is automatically selected as the current time zone of the system. * Time zone is automatically selected as the current time zone of the system.
@ -61,7 +61,7 @@ public extension Date {
func format(with dateStyle: DateFormatter.Style, locale: Locale) -> String { func format(with dateStyle: DateFormatter.Style, locale: Locale) -> String {
return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: locale) return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: locale)
} }
/** /**
* Get string representation of date. * Get string representation of date.
* Locale and time zone are automatically selected as the current locale and time zone of the system. * Locale and time zone are automatically selected as the current locale and time zone of the system.
@ -77,10 +77,9 @@ public extension Date {
return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent) return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent)
#endif #endif
} }
// MARK: - Formatted Date - String // MARK: - Formatted Date - String
/** /**
* Get string representation of date. * Get string representation of date.
* *
@ -95,10 +94,10 @@ public extension Date {
dateFormatter.dateFormat = dateFormat dateFormatter.dateFormat = dateFormat
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
dateFormatter.locale = locale dateFormatter.locale = locale
return dateFormatter.string(from: self) return dateFormatter.string(from: self)
} }
/** /**
* Get string representation of date. * Get string representation of date.
* Locale is automatically selected as the current locale of the system. * Locale is automatically selected as the current locale of the system.
@ -115,7 +114,7 @@ public extension Date {
return format(with: dateFormat, timeZone: timeZone, locale: Locale.autoupdatingCurrent) return format(with: dateFormat, timeZone: timeZone, locale: Locale.autoupdatingCurrent)
#endif #endif
} }
/** /**
* Get string representation of date. * Get string representation of date.
* Time zone is automatically selected as the current time zone of the system. * Time zone is automatically selected as the current time zone of the system.
@ -128,7 +127,7 @@ public extension Date {
func format(with dateFormat: String, locale: Locale) -> String { func format(with dateFormat: String, locale: Locale) -> String {
return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: locale) return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: locale)
} }
/** /**
* Get string representation of date. * Get string representation of date.
* Locale and time zone are automatically selected as the current locale and time zone of the system. * Locale and time zone are automatically selected as the current locale and time zone of the system.

24
Clocker/Dependencies/Date Additions/Date+Inits.swift

@ -14,9 +14,9 @@ import Foundation
*/ */
public extension Date { public extension Date {
// MARK: - Initializers // MARK: - Initializers
/** /**
* Init date with components. * Init date with components.
* *
@ -35,14 +35,14 @@ public extension Date {
dateComponents.hour = hour dateComponents.hour = hour
dateComponents.minute = minute dateComponents.minute = minute
dateComponents.second = second dateComponents.second = second
guard let date = Calendar.current.date(from: dateComponents) else { guard let date = Calendar.current.date(from: dateComponents) else {
self = Date() self = Date()
return return
} }
self = date self = date
} }
/** /**
* Init date with components. Hour, minutes, and seconds set to zero. * Init date with components. Hour, minutes, and seconds set to zero.
* *
@ -53,7 +53,7 @@ public extension Date {
init(year: Int, month: Int, day: Int) { init(year: Int, month: Int, day: Int) {
self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0) self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0)
} }
/** /**
* Init date from string, given a format string, according to Apple's date formatting guide, and time zone. * Init date from string, given a format string, according to Apple's date formatting guide, and time zone.
* *
@ -63,18 +63,18 @@ public extension Date {
*/ */
init(dateString: String, format: String, timeZone: TimeZone) { init(dateString: String, format: String, timeZone: TimeZone) {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none; dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none; dateFormatter.timeStyle = .none
dateFormatter.timeZone = timeZone; dateFormatter.timeZone = timeZone
dateFormatter.dateFormat = format; dateFormatter.dateFormat = format
guard let date = dateFormatter.date(from: dateString) else { guard let date = dateFormatter.date(from: dateString) else {
self = Date() self = Date()
return return
} }
self = date self = date
} }
/** /**
* Init date from string, given a format string, according to Apple's date formatting guide. * Init date from string, given a format string, according to Apple's date formatting guide.
* Time Zone automatically selected as the current time zone. * Time Zone automatically selected as the current time zone.
@ -84,5 +84,5 @@ public extension Date {
*/ */
init (dateString: String, format: String) { init (dateString: String, format: String) {
self.init(dateString: dateString, format: format, timeZone: TimeZone.autoupdatingCurrent) self.init(dateString: dateString, format: format, timeZone: TimeZone.autoupdatingCurrent)
} }
} }

40
Clocker/Dependencies/Date Additions/Date+Manipulations.swift

@ -12,9 +12,9 @@ import Foundation
* Extends the Date class by adding manipulation methods for transforming dates * Extends the Date class by adding manipulation methods for transforming dates
*/ */
public extension Date { public extension Date {
// MARK: - StartOf // MARK: - StartOf
/** /**
* Return a date set to the start of a given component. * Return a date set to the start of a given component.
* *
@ -24,11 +24,10 @@ public extension Date {
* with all smaller components set to their minimum * with all smaller components set to their minimum
*/ */
func start(of component: Component) -> Date { func start(of component: Component) -> Date {
var newDate = self; var newDate = self
if component == .second { if component == .second {
newDate.second(self.second) newDate.second(self.second)
} } else if component == .minute {
else if component == .minute {
newDate.second(0) newDate.second(0)
} else if component == .hour { } else if component == .hour {
newDate.second(0) newDate.second(0)
@ -51,7 +50,7 @@ public extension Date {
} }
return newDate return newDate
} }
/** /**
* Return a date set to the end of a given component. * Return a date set to the end of a given component.
* *
@ -61,12 +60,11 @@ public extension Date {
* with all smaller components set to their maximum * with all smaller components set to their maximum
*/ */
func end(of component: Component) -> Date { func end(of component: Component) -> Date {
var newDate = self; var newDate = self
if component == .second { if component == .second {
newDate.second(newDate.second + 1) newDate.second(newDate.second + 1)
newDate = newDate - 0.001 newDate = newDate - 0.001
} } else if component == .minute {
else if component == .minute {
newDate.second(60) newDate.second(60)
newDate = newDate - 0.001 newDate = newDate - 0.001
} else if component == .hour { } else if component == .hour {
@ -92,10 +90,10 @@ public extension Date {
newDate.month(12) newDate.month(12)
newDate.day(31) newDate.day(31)
} }
return newDate return newDate
} }
func daysInMonth(date: Date) -> Int { func daysInMonth(date: Date) -> Int {
let month = date.month let month = date.month
if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 { if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 {
@ -112,10 +110,9 @@ public extension Date {
return 30 return 30
} }
} }
// MARK: - Addition / Subtractions // MARK: - Addition / Subtractions
/** /**
* # Add (TimeChunk to Date) * # Add (TimeChunk to Date)
* Increase a date by the value of a given `TimeChunk`. * Increase a date by the value of a given `TimeChunk`.
@ -136,7 +133,7 @@ public extension Date {
components.second = chunk.seconds components.second = chunk.seconds
return calendar.date(byAdding: components, to: self)! return calendar.date(byAdding: components, to: self)!
} }
/** /**
* # Subtract (TimeChunk from Date) * # Subtract (TimeChunk from Date)
* Decrease a date by the value of a given `TimeChunk`. * Decrease a date by the value of a given `TimeChunk`.
@ -157,36 +154,35 @@ public extension Date {
components.second = -chunk.seconds components.second = -chunk.seconds
return calendar.date(byAdding: components, to: self)! return calendar.date(byAdding: components, to: self)!
} }
// MARK: - Operator Overloads // MARK: - Operator Overloads
/** /**
* Operator overload for adding a `TimeChunk` to a date. * Operator overload for adding a `TimeChunk` to a date.
*/ */
static func +(leftAddend: Date, rightAddend: TimeChunk) -> Date { static func +(leftAddend: Date, rightAddend: TimeChunk) -> Date {
return leftAddend.add(rightAddend) return leftAddend.add(rightAddend)
} }
/** /**
* Operator overload for subtracting a `TimeChunk` from a date. * Operator overload for subtracting a `TimeChunk` from a date.
*/ */
static func -(minuend: Date, subtrahend: TimeChunk) -> Date { static func -(minuend: Date, subtrahend: TimeChunk) -> Date {
return minuend.subtract(subtrahend) return minuend.subtract(subtrahend)
} }
/** /**
* Operator overload for adding a `TimeInterval` to a date. * Operator overload for adding a `TimeInterval` to a date.
*/ */
static func +(leftAddend: Date, rightAddend: Int) -> Date { static func +(leftAddend: Date, rightAddend: Int) -> Date {
return leftAddend.addingTimeInterval((TimeInterval(rightAddend))) return leftAddend.addingTimeInterval((TimeInterval(rightAddend)))
} }
/** /**
* Operator overload for subtracting a `TimeInterval` from a date. * Operator overload for subtracting a `TimeInterval` from a date.
*/ */
static func -(minuend: Date, subtrahend: Int) -> Date { static func -(minuend: Date, subtrahend: Int) -> Date {
return minuend.addingTimeInterval(-(TimeInterval(subtrahend))) return minuend.addingTimeInterval(-(TimeInterval(subtrahend)))
} }
} }

191
Clocker/Dependencies/Date Additions/Date+TimeAgo.swift

@ -13,9 +13,9 @@ import Foundation
* time in String format. * time in String format.
*/ */
public extension Date { public extension Date {
//MARK: - Time Ago // MARK: - Time Ago
/** /**
* Takes in a date and returns a string with the most convenient unit of time representing * Takes in a date and returns a string with the most convenient unit of time representing
* how far in the past that date is from now. * how far in the past that date is from now.
@ -24,10 +24,10 @@ public extension Date {
* *
* - returns String - Formatted return string * - returns String - Formatted return string
*/ */
static func timeAgo(since date:Date) -> String{ static func timeAgo(since date:Date) -> String {
return date.timeAgo(since: Date(), numericDates: false, numericTimes: false) return date.timeAgo(since: Date(), numericDates: false, numericTimes: false)
} }
/** /**
* Takes in a date and returns a shortened string with the most convenient unit of time representing * Takes in a date and returns a shortened string with the most convenient unit of time representing
* how far in the past that date is from now. * how far in the past that date is from now.
@ -39,7 +39,7 @@ public extension Date {
static func shortTimeAgo(since date:Date) -> String { static func shortTimeAgo(since date:Date) -> String {
return date.shortTimeAgo(since:Date()) return date.shortTimeAgo(since:Date())
} }
/** /**
* Returns a string with the most convenient unit of time representing * Returns a string with the most convenient unit of time representing
* how far in the past that date is from now. * how far in the past that date is from now.
@ -49,7 +49,7 @@ public extension Date {
var timeAgoSinceNow: String { var timeAgoSinceNow: String {
return self.timeAgo(since:Date()) return self.timeAgo(since:Date())
} }
/** /**
* Returns a shortened string with the most convenient unit of time representing * Returns a shortened string with the most convenient unit of time representing
* how far in the past that date is from now. * how far in the past that date is from now.
@ -59,184 +59,156 @@ public extension Date {
var shortTimeAgoSinceNow: String { var shortTimeAgoSinceNow: String {
return self.shortTimeAgo(since:Date()) return self.shortTimeAgo(since:Date())
} }
func timeAgo(since date:Date, numericDates: Bool = false, numericTimes: Bool = false) -> String { func timeAgo(since date:Date, numericDates: Bool = false, numericTimes: Bool = false) -> String {
let calendar = NSCalendar.current let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year]) let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year])
let earliest = self.earlierDate(date) let earliest = self.earlierDate(date)
let latest = (earliest == self) ? date : self //Should be triple equals, but not extended to Date at this time let latest = (earliest == self) ? date : self //Should be triple equals, but not extended to Date at this time
let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let components = calendar.dateComponents(unitFlags, from: earliest, to: latest)
let yesterday = date.subtract(1.days) let yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day let isYesterday = yesterday.day == self.day
//Not Yet Implemented/Optional //Not Yet Implemented/Optional
//The following strings are present in the translation files but lack logic as of 2014.04.05 //The following strings are present in the translation files but lack logic as of 2014.04.05
//@"Today", @"This week", @"This month", @"This year" //@"Today", @"This week", @"This month", @"This year"
//and @"This morning", @"This afternoon" //and @"This morning", @"This afternoon"
if (components.year! >= 2) { if (components.year! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!) return self.logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!)
} } else if (components.year! >= 1) {
else if (components.year! >= 1) {
if (numericDates) { if (numericDates) {
return DateToolsLocalizedStrings("1 year ago"); return DateToolsLocalizedStrings("1 year ago")
} }
return DateToolsLocalizedStrings("Last year"); return DateToolsLocalizedStrings("Last year")
} } else if (components.month! >= 2) {
else if (components.month! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!) return self.logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!)
} } else if (components.month! >= 1) {
else if (components.month! >= 1) {
if (numericDates) { if (numericDates) {
return DateToolsLocalizedStrings("1 month ago"); return DateToolsLocalizedStrings("1 month ago")
} }
return DateToolsLocalizedStrings("Last month"); return DateToolsLocalizedStrings("Last month")
} } else if (components.weekOfYear! >= 2) {
else if (components.weekOfYear! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!) return self.logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!)
} } else if (components.weekOfYear! >= 1) {
else if (components.weekOfYear! >= 1) {
if (numericDates) { if (numericDates) {
return DateToolsLocalizedStrings("1 week ago"); return DateToolsLocalizedStrings("1 week ago")
} }
return DateToolsLocalizedStrings("Last week"); return DateToolsLocalizedStrings("Last week")
} } else if (components.day! >= 2) {
else if (components.day! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!) return self.logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!)
} } else if (isYesterday) {
else if (isYesterday) {
if (numericDates) { if (numericDates) {
return DateToolsLocalizedStrings("1 day ago"); return DateToolsLocalizedStrings("1 day ago")
} }
return DateToolsLocalizedStrings("Yesterday"); return DateToolsLocalizedStrings("Yesterday")
} } else if (components.hour! >= 2) {
else if (components.hour! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!) return self.logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!)
} } else if (components.hour! >= 1) {
else if (components.hour! >= 1) {
if (numericTimes) { if (numericTimes) {
return DateToolsLocalizedStrings("1 hour ago"); return DateToolsLocalizedStrings("1 hour ago")
} }
return DateToolsLocalizedStrings("An hour ago"); return DateToolsLocalizedStrings("An hour ago")
} } else if (components.minute! >= 2) {
else if (components.minute! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@minutes ago", value: components.minute!) return self.logicalLocalizedStringFromFormat(format: "%%d %@minutes ago", value: components.minute!)
} } else if (components.minute! >= 1) {
else if (components.minute! >= 1) {
if (numericTimes) { if (numericTimes) {
return DateToolsLocalizedStrings("1 minute ago"); return DateToolsLocalizedStrings("1 minute ago")
} }
return DateToolsLocalizedStrings("A minute ago"); return DateToolsLocalizedStrings("A minute ago")
} } else if (components.second! >= 3) {
else if (components.second! >= 3) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!) return self.logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!)
} } else {
else {
if (numericTimes) { if (numericTimes) {
return DateToolsLocalizedStrings("1 second ago"); return DateToolsLocalizedStrings("1 second ago")
} }
return DateToolsLocalizedStrings("Just now"); return DateToolsLocalizedStrings("Just now")
} }
} }
func shortTimeAgo(since date:Date) -> String { func shortTimeAgo(since date:Date) -> String {
let calendar = NSCalendar.current let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year]) let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year])
let earliest = self.earlierDate(date) let earliest = self.earlierDate(date)
let latest = (earliest == self) ? date : self //Should pbe triple equals, but not extended to Date at this time let latest = (earliest == self) ? date : self //Should pbe triple equals, but not extended to Date at this time
let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let components = calendar.dateComponents(unitFlags, from: earliest, to: latest)
let yesterday = date.subtract(1.days) let yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day let isYesterday = yesterday.day == self.day
if (components.year! >= 1) { if (components.year! >= 1) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@y", value: components.year!) return self.logicalLocalizedStringFromFormat(format: "%%d%@y", value: components.year!)
} } else if (components.month! >= 1) {
else if (components.month! >= 1) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@M", value: components.month!) return self.logicalLocalizedStringFromFormat(format: "%%d%@M", value: components.month!)
} } else if (components.weekOfYear! >= 1) {
else if (components.weekOfYear! >= 1) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@w", value: components.weekOfYear!) return self.logicalLocalizedStringFromFormat(format: "%%d%@w", value: components.weekOfYear!)
} } else if (components.day! >= 2) {
else if (components.day! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: components.day!) return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: components.day!)
} } else if (isYesterday) {
else if (isYesterday) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: 1) return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: 1)
} } else if (components.hour! >= 1) {
else if (components.hour! >= 1) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@h", value: components.hour!) return self.logicalLocalizedStringFromFormat(format: "%%d%@h", value: components.hour!)
} } else if (components.minute! >= 1) {
else if (components.minute! >= 1) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@m", value: components.minute!) return self.logicalLocalizedStringFromFormat(format: "%%d%@m", value: components.minute!)
} } else if (components.second! >= 3) {
else if (components.second! >= 3) {
return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!)
} } else {
else {
return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!)
//return DateToolsLocalizedStrings(@"Now"); //string not yet translated 2014.04.05 //return DateToolsLocalizedStrings(@"Now"); //string not yet translated 2014.04.05
} }
} }
private func logicalLocalizedStringFromFormat(format: String, value: Int) -> String {
private func logicalLocalizedStringFromFormat(format: String, value: Int) -> String{
#if os(Linux) #if os(Linux)
let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value)) as! CVarArg) // this may not work, unclear!! let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value)) as! CVarArg) // this may not work, unclear!!
#else #else
let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value))) let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value)))
#endif #endif
return String.init(format: DateToolsLocalizedStrings(localeFormat), value) return String.init(format: DateToolsLocalizedStrings(localeFormat), value)
} }
private func getLocaleFormatUnderscoresWithValue(_ value: Double) -> String {
private func getLocaleFormatUnderscoresWithValue(_ value: Double) -> String{
let localCode = Bundle.main.preferredLocalizations[0] let localCode = Bundle.main.preferredLocalizations[0]
if (localCode == "ru" || localCode == "uk") { if (localCode == "ru" || localCode == "uk") {
let XY = Int(floor(value).truncatingRemainder(dividingBy: 100)) let XY = Int(floor(value).truncatingRemainder(dividingBy: 100))
let Y = Int(floor(value).truncatingRemainder(dividingBy: 10)) let Y = Int(floor(value).truncatingRemainder(dividingBy: 10))
if(Y == 0 || Y > 4 || (XY > 10 && XY < 15)) { if(Y == 0 || Y > 4 || (XY > 10 && XY < 15)) {
return "" return ""
} }
if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) { if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) {
return "_" return "_"
} }
if(Y == 1 && XY != 11) { if(Y == 1 && XY != 11) {
return "__" return "__"
} }
} }
return "" return ""
} }
// MARK: - Localization // MARK: - Localization
private func DateToolsLocalizedStrings(_ string: String) -> String { private func DateToolsLocalizedStrings(_ string: String) -> String {
//let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle") //let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle")
//let bundelPath = Bundle(path:classBundle)! //let bundelPath = Bundle(path:classBundle)!
#if os(Linux) #if os(Linux)
// NSLocalizedString() is not available yet, see: https://github.com/apple/swift-corelibs-foundation/blob/16f83ddcd311b768e30a93637af161676b0a5f2f/Foundation/NSData.swift // NSLocalizedString() is not available yet, see: https://github.com/apple/swift-corelibs-foundation/blob/16f83ddcd311b768e30a93637af161676b0a5f2f/Foundation/NSData.swift
@ -246,10 +218,9 @@ public extension Date {
return NSLocalizedString(string, tableName: "DateTools", bundle: Bundle.dateToolsBundle(), value: "", comment: "") return NSLocalizedString(string, tableName: "DateTools", bundle: Bundle.dateToolsBundle(), value: "", comment: "")
#endif #endif
} }
// MARK: - Date Earlier/Later // MARK: - Date Earlier/Later
/** /**
* Return the earlier of two dates, between self and a given date. * Return the earlier of two dates, between self and a given date.
* *
@ -257,10 +228,10 @@ public extension Date {
* *
* - returns: The date that is earlier * - returns: The date that is earlier
*/ */
func earlierDate(_ date:Date) -> Date{ func earlierDate(_ date:Date) -> Date {
return (self.timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : date return (self.timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : date
} }
/** /**
* Return the later of two dates, between self and a given date. * Return the later of two dates, between self and a given date.
* *
@ -268,8 +239,8 @@ public extension Date {
* *
* - returns: The date that is later * - returns: The date that is later
*/ */
func laterDate(_ date:Date) -> Date{ func laterDate(_ date:Date) -> Date {
return (self.timeIntervalSince1970 >= date.timeIntervalSince1970) ? self : date return (self.timeIntervalSince1970 >= date.timeIntervalSince1970) ? self : date
} }
} }

1
Clocker/Dependencies/Date Additions/Enums.swift

@ -34,7 +34,6 @@ public enum Relation {
case none // One or more of the dates does not exist case none // One or more of the dates does not exist
} }
/** /**
* Whether the time period is Open or Closed * Whether the time period is Open or Closed
* *

18
Clocker/Dependencies/Date Additions/Integer+DateTools.swift

@ -9,51 +9,51 @@
import Foundation import Foundation
public extension Int { public extension Int {
//MARK: TimePeriod // MARK: TimePeriod
/** /**
* A `TimeChunk` with its seconds component set to the value of self * A `TimeChunk` with its seconds component set to the value of self
*/ */
var seconds: TimeChunk { var seconds: TimeChunk {
return TimeChunk(seconds: self, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: self, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
} }
/** /**
* A `TimeChunk` with its minutes component set to the value of self * A `TimeChunk` with its minutes component set to the value of self
*/ */
var minutes: TimeChunk { var minutes: TimeChunk {
return TimeChunk(seconds: 0, minutes: self, hours: 0, days: 0, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: self, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
} }
/** /**
* A `TimeChunk` with its hours component set to the value of self * A `TimeChunk` with its hours component set to the value of self
*/ */
var hours: TimeChunk { var hours: TimeChunk {
return TimeChunk(seconds: 0, minutes: 0, hours: self, days: 0, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: self, days: 0, weeks: 0, months: 0, years: 0)
} }
/** /**
* A `TimeChunk` with its days component set to the value of self * A `TimeChunk` with its days component set to the value of self
*/ */
var days: TimeChunk { var days: TimeChunk {
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: self, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: self, weeks: 0, months: 0, years: 0)
} }
/** /**
* A `TimeChunk` with its weeks component set to the value of self * A `TimeChunk` with its weeks component set to the value of self
*/ */
var weeks: TimeChunk { var weeks: TimeChunk {
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: self, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: self, months: 0, years: 0)
} }
/** /**
* A `TimeChunk` with its months component set to the value of self * A `TimeChunk` with its months component set to the value of self
*/ */
var months: TimeChunk { var months: TimeChunk {
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: self, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: self, years: 0)
} }
/** /**
* A `TimeChunk` with its years component set to the value of self * A `TimeChunk` with its years component set to the value of self
*/ */

63
Clocker/Dependencies/Date Additions/TimeChunk.swift

@ -20,9 +20,9 @@ import Foundation
* For more information about the utility of TimeChunks in relation to Dates, see the `Date+Manipulations` class. * For more information about the utility of TimeChunks in relation to Dates, see the `Date+Manipulations` class.
*/ */
public struct TimeChunk { public struct TimeChunk {
// MARK: - Variables // MARK: - Variables
public var seconds = 0 public var seconds = 0
public var minutes = 0 public var minutes = 0
public var hours = 0 public var hours = 0
@ -30,9 +30,9 @@ public struct TimeChunk {
public var weeks = 0 public var weeks = 0
public var months = 0 public var months = 0
public var years = 0 public var years = 0
public init() {} public init() {}
public init(seconds: Int, minutes: Int, hours: Int, days: Int, weeks: Int, months: Int, years: Int) { public init(seconds: Int, minutes: Int, hours: Int, days: Int, weeks: Int, months: Int, years: Int) {
self.seconds = seconds self.seconds = seconds
self.minutes = minutes self.minutes = minutes
@ -42,10 +42,9 @@ public struct TimeChunk {
self.months = months self.months = months
self.years = years self.years = years
} }
// MARK: - Comparisons // MARK: - Comparisons
/** /**
* Check if two `TimeChunk`s are equal. * Check if two `TimeChunk`s are equal.
* *
@ -56,10 +55,9 @@ public struct TimeChunk {
public func equals(chunk: TimeChunk) -> Bool { public func equals(chunk: TimeChunk) -> Bool {
return (seconds == chunk.seconds && minutes == chunk.minutes && hours == chunk.hours && days == chunk.days && weeks == chunk.weeks && months == chunk.months && years == chunk.years) return (seconds == chunk.seconds && minutes == chunk.minutes && hours == chunk.hours && days == chunk.days && weeks == chunk.weeks && months == chunk.months && years == chunk.years)
} }
// MARK: - Conversion // MARK: - Conversion
/** /**
* Generic conversion method. Years are taken to mean * Generic conversion method. Years are taken to mean
* 365 days. This method should not be used for accurate * 365 days. This method should not be used for accurate
@ -124,24 +122,23 @@ public struct TimeChunk {
} }
return 0 return 0
} }
// MARK: - Date Creation // MARK: - Date Creation
/** /**
* Returns the current date decreased by the amount in self * Returns the current date decreased by the amount in self
*/ */
public var earlier: Date { public var earlier: Date {
return earlier(than: Date()) return earlier(than: Date())
} }
/** /**
* Returns the current date increased by the amount in self * Returns the current date increased by the amount in self
*/ */
public var later: Date { public var later: Date {
return later(than: Date()) return later(than: Date())
} }
/** /**
* Returns the given date decreased by the amount in self. * Returns the given date decreased by the amount in self.
* *
@ -152,7 +149,7 @@ public struct TimeChunk {
public func earlier(than date: Date) -> Date { public func earlier(than date: Date) -> Date {
return date.subtract(self) return date.subtract(self)
} }
/** /**
* Returns the given date increased by the amount in self. * Returns the given date increased by the amount in self.
* *
@ -163,11 +160,11 @@ public struct TimeChunk {
public func later(than date: Date) -> Date { public func later(than date: Date) -> Date {
return date.add(self) return date.add(self)
} }
// MARK: - Lengthen / Shorten // MARK: - Lengthen / Shorten
// MARK: In Place // MARK: In Place
/** /**
* Increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * Increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`.
* *
@ -184,10 +181,10 @@ public struct TimeChunk {
newChunk.weeks = weeks + chunk.weeks newChunk.weeks = weeks + chunk.weeks
newChunk.months = months + chunk.months newChunk.months = months + chunk.months
newChunk.years = years + chunk.years newChunk.years = years + chunk.years
return newChunk return newChunk
} }
/** /**
* Decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * Decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`.
* *
@ -204,13 +201,12 @@ public struct TimeChunk {
newChunk.weeks = weeks - chunk.weeks newChunk.weeks = weeks - chunk.weeks
newChunk.months = months - chunk.months newChunk.months = months - chunk.months
newChunk.years = years - chunk.years newChunk.years = years - chunk.years
return newChunk return newChunk
} }
// MARK: Mutation // MARK: Mutation
/** /**
* In place, increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * In place, increase the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`.
* *
@ -225,7 +221,7 @@ public struct TimeChunk {
months += chunk.months months += chunk.months
years += chunk.years years += chunk.years
} }
/** /**
* In place, decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`. * In place, decrease the variables of self (`TimeChunk`) by the variables of the given `TimeChunk`.
* *
@ -240,36 +236,35 @@ public struct TimeChunk {
months -= chunk.months months -= chunk.months
years -= chunk.years years -= chunk.years
} }
// MARK: - Operator Overloads // MARK: - Operator Overloads
/** /**
* Operator overload for adding two `TimeChunk`s * Operator overload for adding two `TimeChunk`s
*/ */
public static func +(leftAddend: TimeChunk, rightAddend: TimeChunk) -> TimeChunk { public static func +(leftAddend: TimeChunk, rightAddend: TimeChunk) -> TimeChunk {
return leftAddend.lengthened(by: rightAddend) return leftAddend.lengthened(by: rightAddend)
} }
/** /**
* Operator overload for subtracting two `TimeChunk`s * Operator overload for subtracting two `TimeChunk`s
*/ */
public static func -(minuend: TimeChunk, subtrahend: TimeChunk) -> TimeChunk { public static func -(minuend: TimeChunk, subtrahend: TimeChunk) -> TimeChunk {
return minuend.shortened(by: subtrahend) return minuend.shortened(by: subtrahend)
} }
/** /**
* Operator overload for checking if two `TimeChunk`s are equal * Operator overload for checking if two `TimeChunk`s are equal
*/ */
public static func ==(left: TimeChunk, right: TimeChunk) -> Bool { public static func ==(left: TimeChunk, right: TimeChunk) -> Bool {
return left.equals(chunk: right) return left.equals(chunk: right)
} }
/** /**
* Operator overload for inverting (negating all variables) a `TimeChunk` * Operator overload for inverting (negating all variables) a `TimeChunk`
*/ */
public static prefix func -(chunk: TimeChunk) -> TimeChunk { public static prefix func -(chunk: TimeChunk) -> TimeChunk {
var invertedChunk = chunk; var invertedChunk = chunk
invertedChunk.seconds = -chunk.seconds invertedChunk.seconds = -chunk.seconds
invertedChunk.minutes = -chunk.minutes invertedChunk.minutes = -chunk.minutes
invertedChunk.hours = -chunk.hours invertedChunk.hours = -chunk.hours
@ -279,5 +274,5 @@ public struct TimeChunk {
invertedChunk.years = -chunk.years invertedChunk.years = -chunk.years
return invertedChunk return invertedChunk
} }
} }

204
Clocker/Dependencies/Date Additions/TimePeriod.swift

@ -8,8 +8,6 @@
import Foundation import Foundation
/** /**
* In DateTools, time periods are represented by the TimePeriod protocol. * In DateTools, time periods are represented by the TimePeriod protocol.
* Required variables and method impleementations are bound below. An inheritable * Required variables and method impleementations are bound below. An inheritable
@ -18,14 +16,14 @@ import Foundation
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/ */
public protocol TimePeriodProtocol { public protocol TimePeriodProtocol {
// MARK: - Variables // MARK: - Variables
/** /**
* The start date for a TimePeriod representing the starting boundary of the time period * The start date for a TimePeriod representing the starting boundary of the time period
*/ */
var beginning: Date? {get set} var beginning: Date? {get set}
/** /**
* The end date for a TimePeriod representing the ending boundary of the time period * The end date for a TimePeriod representing the ending boundary of the time period
*/ */
@ -33,17 +31,16 @@ public protocol TimePeriodProtocol {
} }
public extension TimePeriodProtocol { public extension TimePeriodProtocol {
// MARK: - Information // MARK: - Information
/** /**
* True if the `TimePeriod`'s duration is zero * True if the `TimePeriod`'s duration is zero
*/ */
var isMoment: Bool { var isMoment: Bool {
return self.beginning == self.end return self.beginning == self.end
} }
/** /**
* The duration of the `TimePeriod` in years. * The duration of the `TimePeriod` in years.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -54,7 +51,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in weeks. * The duration of the `TimePeriod` in weeks.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -65,7 +62,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in days. * The duration of the `TimePeriod` in days.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -76,7 +73,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in hours. * The duration of the `TimePeriod` in hours.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -87,7 +84,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in minutes. * The duration of the `TimePeriod` in minutes.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -98,7 +95,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in seconds. * The duration of the `TimePeriod` in seconds.
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
@ -109,7 +106,7 @@ public extension TimePeriodProtocol {
} }
return Int.max return Int.max
} }
/** /**
* The duration of the `TimePeriod` in a time chunk. * The duration of the `TimePeriod` in a time chunk.
* Returns a time chunk with all zeroes if beginning or end are nil. * Returns a time chunk with all zeroes if beginning or end are nil.
@ -120,7 +117,7 @@ public extension TimePeriodProtocol {
} }
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
} }
/** /**
* The length of time between the beginning and end dates of the * The length of time between the beginning and end dates of the
* `TimePeriod` as a `TimeInterval`. * `TimePeriod` as a `TimeInterval`.
@ -129,13 +126,12 @@ public extension TimePeriodProtocol {
if self.beginning != nil && self.end != nil { if self.beginning != nil && self.end != nil {
return abs(self.beginning!.timeIntervalSince(self.end!)) return abs(self.beginning!.timeIntervalSince(self.end!))
} }
return TimeInterval(Double.greatestFiniteMagnitude) return TimeInterval(Double.greatestFiniteMagnitude)
} }
// MARK: - Time Period Relationships // MARK: - Time Period Relationships
/** /**
* The relationship of the self `TimePeriod` to the given `TimePeriod`. * The relationship of the self `TimePeriod` to the given `TimePeriod`.
* Relations are stored in Enums.swift. Formal defnitions available in the provided * Relations are stored in Enums.swift. Formal defnitions available in the provided
@ -152,53 +148,41 @@ public extension TimePeriodProtocol {
if (self.beginning != nil && self.end != nil && period.beginning != nil && period.end != nil) { if (self.beginning != nil && self.end != nil && period.beginning != nil && period.end != nil) {
//Make sure time periods are of positive durations //Make sure time periods are of positive durations
if (self.beginning!.isEarlier(than: self.end!) && period.beginning!.isEarlier(than: period.end!)) { if (self.beginning!.isEarlier(than: self.end!) && period.beginning!.isEarlier(than: period.end!)) {
//Make comparisons //Make comparisons
if (period.end!.isEarlier(than: self.beginning!)) { if (period.end!.isEarlier(than: self.beginning!)) {
return .after return .after
} } else if (period.end!.equals(self.beginning!)) {
else if (period.end!.equals(self.beginning!)) {
return .startTouching return .startTouching
} } else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) {
else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) {
return .startInside return .startInside
} } else if (period.beginning!.equals(self.beginning!) && period.end!.isLater(than: self.end!)) {
else if (period.beginning!.equals(self.beginning!) && period.end!.isLater(than: self.end!)) {
return .insideStartTouching return .insideStartTouching
} } else if (period.beginning!.equals(self.beginning!) && period.end!.isEarlier(than: self.end!)) {
else if (period.beginning!.equals(self.beginning!) && period.end!.isEarlier(than: self.end!)) {
return .enclosingStartTouching return .enclosingStartTouching
} } else if (period.beginning!.isLater(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) {
else if (period.beginning!.isLater(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) {
return .enclosing return .enclosing
} } else if (period.beginning!.isLater(than: self.beginning!) && period.end!.equals(self.end!)) {
else if (period.beginning!.isLater(than: self.beginning!) && period.end!.equals(self.end!)) {
return .enclosingEndTouching return .enclosingEndTouching
} } else if (period.beginning!.equals(self.beginning!) && period.end!.equals(self.end!)) {
else if (period.beginning!.equals(self.beginning!) && period.end!.equals(self.end!)) {
return .exactMatch return .exactMatch
} } else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isLater(than: self.end!)) {
else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isLater(than: self.end!)) {
return .inside return .inside
} } else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.equals(self.end!)) {
else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.equals(self.end!)) {
return .insideEndTouching return .insideEndTouching
} } else if (period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)) {
else if (period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)) {
return .endInside return .endInside
} } else if (period.beginning!.equals(self.end!) && period.end!.isLater(than: self.end!)) {
else if (period.beginning!.equals(self.end!) && period.end!.isLater(than: self.end!)) {
return .endTouching return .endTouching
} } else if (period.beginning!.isLater(than: self.end!)) {
else if (period.beginning!.isLater(than: self.end!)) {
return .before return .before
} }
} }
} }
return .none; return .none
} }
/** /**
* If `self.beginning` and `self.end` are equal to the beginning and end of the * If `self.beginning` and `self.end` are equal to the beginning and end of the
* given `TimePeriod`. * given `TimePeriod`.
@ -210,7 +194,7 @@ public extension TimePeriodProtocol {
func equals(_ period: TimePeriodProtocol) -> Bool { func equals(_ period: TimePeriodProtocol) -> Bool {
return self.beginning == period.beginning && self.end == period.end return self.beginning == period.beginning && self.end == period.end
} }
/** /**
* If the given `TimePeriod`'s beginning is before `self.beginning` and * If the given `TimePeriod`'s beginning is before `self.beginning` and
* if the given 'TimePeriod`'s end is after `self.end`. * if the given 'TimePeriod`'s end is after `self.end`.
@ -222,7 +206,7 @@ public extension TimePeriodProtocol {
func isInside(of period: TimePeriodProtocol) -> Bool { func isInside(of period: TimePeriodProtocol) -> Bool {
return period.beginning!.isEarlierThanOrEqual(to: self.beginning!) && period.end!.isLaterThanOrEqual(to: self.end!) return period.beginning!.isEarlierThanOrEqual(to: self.beginning!) && period.end!.isLaterThanOrEqual(to: self.end!)
} }
/** /**
* If the given Date is after `self.beginning` and before `self.end`. * If the given Date is after `self.beginning` and before `self.end`.
* *
@ -234,14 +218,13 @@ public extension TimePeriodProtocol {
func contains(_ date: Date, interval: Interval) -> Bool { func contains(_ date: Date, interval: Interval) -> Bool {
if (interval == .open) { if (interval == .open) {
return self.beginning!.isEarlier(than: date) && self.end!.isLater(than: date) return self.beginning!.isEarlier(than: date) && self.end!.isLater(than: date)
} } else if (interval == .closed) {
else if (interval == .closed){
return (self.beginning!.isEarlierThanOrEqual(to: date) && self.end!.isLaterThanOrEqual(to: date)) return (self.beginning!.isEarlierThanOrEqual(to: date) && self.end!.isLaterThanOrEqual(to: date))
} }
return false return false
} }
/** /**
* If the given `TimePeriod`'s beginning is after `self.beginning` and * If the given `TimePeriod`'s beginning is after `self.beginning` and
* if the given 'TimePeriod`'s after is after `self.end`. * if the given 'TimePeriod`'s after is after `self.end`.
@ -253,7 +236,7 @@ public extension TimePeriodProtocol {
func contains(_ period: TimePeriodProtocol) -> Bool { func contains(_ period: TimePeriodProtocol) -> Bool {
return self.beginning!.isEarlierThanOrEqual(to: period.beginning!) && self.end!.isLaterThanOrEqual(to: period.end!) return self.beginning!.isEarlierThanOrEqual(to: period.beginning!) && self.end!.isLaterThanOrEqual(to: period.end!)
} }
/** /**
* If self and the given `TimePeriod` share any sub-`TimePeriod`. * If self and the given `TimePeriod` share any sub-`TimePeriod`.
* *
@ -267,16 +250,16 @@ public extension TimePeriodProtocol {
return true return true
} }
//Enclosing //Enclosing
else if (period.beginning!.isLaterThanOrEqual(to: self.beginning!) && period.end!.isEarlierThanOrEqual(to: self.end!)){ else if (period.beginning!.isLaterThanOrEqual(to: self.beginning!) && period.end!.isEarlierThanOrEqual(to: self.end!)) {
return true return true
} }
//Inside -> Out //Inside -> Out
else if(period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)){ else if(period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)) {
return true return true
} }
return false return false
} }
/** /**
* If self and the given `TimePeriod` overlap or the period's edges touch. * If self and the given `TimePeriod` overlap or the period's edges touch.
* *
@ -287,7 +270,7 @@ public extension TimePeriodProtocol {
func intersects(with period: TimePeriodProtocol) -> Bool { func intersects(with period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) != .after && self.relation(to: period) != .before return self.relation(to: period) != .after && self.relation(to: period) != .before
} }
/** /**
* If self and the given `TimePeriod` have no overlap or touching edges. * If self and the given `TimePeriod` have no overlap or touching edges.
* *
@ -298,7 +281,7 @@ public extension TimePeriodProtocol {
func hasGap(between period: TimePeriodProtocol) -> Bool { func hasGap(between period: TimePeriodProtocol) -> Bool {
return self.isBefore(period: period) || self.isAfter(period: period) return self.isBefore(period: period) || self.isAfter(period: period)
} }
/** /**
* The period of time between self and the given `TimePeriod` not contained by either. * The period of time between self and the given `TimePeriod` not contained by either.
* *
@ -308,14 +291,13 @@ public extension TimePeriodProtocol {
*/ */
func gap(between period: TimePeriodProtocol) -> TimeInterval { func gap(between period: TimePeriodProtocol) -> TimeInterval {
if (self.end!.isEarlier(than: period.beginning!)) { if (self.end!.isEarlier(than: period.beginning!)) {
return abs(self.end!.timeIntervalSince(period.beginning!)); return abs(self.end!.timeIntervalSince(period.beginning!))
} } else if (period.end!.isEarlier(than: self.beginning!)) {
else if (period.end!.isEarlier(than: self.beginning!)){ return abs(period.end!.timeIntervalSince(self.beginning!))
return abs(period.end!.timeIntervalSince(self.beginning!));
} }
return 0 return 0
} }
/** /**
* The period of time between self and the given `TimePeriod` not contained by either * The period of time between self and the given `TimePeriod` not contained by either
* as a `TimeChunk`. * as a `TimeChunk`.
@ -330,7 +312,7 @@ public extension TimePeriodProtocol {
} }
return nil return nil
} }
/** /**
* If self is after the given `TimePeriod` chronologically. (A gap must exist between the two). * If self is after the given `TimePeriod` chronologically. (A gap must exist between the two).
* *
@ -341,7 +323,7 @@ public extension TimePeriodProtocol {
func isAfter(period: TimePeriodProtocol) -> Bool { func isAfter(period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) == .after return self.relation(to: period) == .after
} }
/** /**
* If self is before the given `TimePeriod` chronologically. (A gap must exist between the two). * If self is before the given `TimePeriod` chronologically. (A gap must exist between the two).
* *
@ -352,11 +334,11 @@ public extension TimePeriodProtocol {
func isBefore(period: TimePeriodProtocol) -> Bool { func isBefore(period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) == .before return self.relation(to: period) == .before
} }
// MARK: - Shifts // MARK: - Shifts
//MARK: In Place // MARK: In Place
/** /**
* In place, shift the `TimePeriod` by a `TimeInterval` * In place, shift the `TimePeriod` by a `TimeInterval`
* *
@ -366,7 +348,7 @@ public extension TimePeriodProtocol {
self.beginning?.addTimeInterval(timeInterval) self.beginning?.addTimeInterval(timeInterval)
self.end?.addTimeInterval(timeInterval) self.end?.addTimeInterval(timeInterval)
} }
/** /**
* In place, shift the `TimePeriod` by a `TimeChunk` * In place, shift the `TimePeriod` by a `TimeChunk`
* *
@ -376,12 +358,11 @@ public extension TimePeriodProtocol {
self.beginning = self.beginning?.add(chunk) self.beginning = self.beginning?.add(chunk)
self.end = self.end?.add(chunk) self.end = self.end?.add(chunk)
} }
// MARK: - Lengthen / Shorten // MARK: - Lengthen / Shorten
// MARK: In Place // MARK: In Place
/** /**
* In place, lengthen the `TimePeriod`, anchored at the beginning, end or center * In place, lengthen the `TimePeriod`, anchored at the beginning, end or center
* *
@ -402,7 +383,7 @@ public extension TimePeriodProtocol {
break break
} }
} }
/** /**
* In place, lengthen the `TimePeriod`, anchored at the beginning or end * In place, lengthen the `TimePeriod`, anchored at the beginning or end
* *
@ -423,7 +404,7 @@ public extension TimePeriodProtocol {
break break
} }
} }
/** /**
* In place, shorten the `TimePeriod`, anchored at the beginning, end or center * In place, shorten the `TimePeriod`, anchored at the beginning, end or center
* *
@ -444,7 +425,7 @@ public extension TimePeriodProtocol {
break break
} }
} }
/** /**
* In place, shorten the `TimePeriod`, anchored at the beginning or end * In place, shorten the `TimePeriod`, anchored at the beginning or end
* *
@ -475,58 +456,56 @@ public extension TimePeriodProtocol {
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/ */
open class TimePeriod: TimePeriodProtocol { open class TimePeriod: TimePeriodProtocol {
// MARK: - Variables // MARK: - Variables
/** /**
* The start date for a TimePeriod representing the starting boundary of the time period * The start date for a TimePeriod representing the starting boundary of the time period
*/ */
public var beginning: Date? public var beginning: Date?
/** /**
* The end date for a TimePeriod representing the ending boundary of the time period * The end date for a TimePeriod representing the ending boundary of the time period
*/ */
public var end: Date? public var end: Date?
// MARK: - Initializers // MARK: - Initializers
init() { init() {
} }
init(beginning: Date?, end: Date?) { init(beginning: Date?, end: Date?) {
self.beginning = beginning self.beginning = beginning
self.end = end self.end = end
} }
init(beginning: Date, duration: TimeInterval) { init(beginning: Date, duration: TimeInterval) {
self.beginning = beginning self.beginning = beginning
self.end = beginning + duration self.end = beginning + duration
} }
init(end: Date, duration: TimeInterval) { init(end: Date, duration: TimeInterval) {
self.end = end self.end = end
self.beginning = end.addingTimeInterval(-duration) self.beginning = end.addingTimeInterval(-duration)
} }
init(beginning: Date, chunk: TimeChunk) { init(beginning: Date, chunk: TimeChunk) {
self.beginning = beginning self.beginning = beginning
self.end = beginning + chunk self.end = beginning + chunk
} }
init(end: Date, chunk: TimeChunk) { init(end: Date, chunk: TimeChunk) {
self.end = end self.end = end
self.beginning = end - chunk self.beginning = end - chunk
} }
init(chunk: TimeChunk) { init(chunk: TimeChunk) {
self.beginning = Date() self.beginning = Date()
self.end = self.beginning?.add(chunk) self.end = self.beginning?.add(chunk)
} }
// MARK: - Shifted // MARK: - Shifted
/** /**
* Shift the `TimePeriod` by a `TimeInterval` * Shift the `TimePeriod` by a `TimeInterval`
* *
@ -540,7 +519,7 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end?.addingTimeInterval(timeInterval) timePeriod.end = self.end?.addingTimeInterval(timeInterval)
return timePeriod return timePeriod
} }
/** /**
* Shift the `TimePeriod` by a `TimeChunk` * Shift the `TimePeriod` by a `TimeChunk`
* *
@ -554,11 +533,11 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end?.add(chunk) timePeriod.end = self.end?.add(chunk)
return timePeriod return timePeriod
} }
// MARK: - Lengthen / Shorten // MARK: - Lengthen / Shorten
// MARK: New // MARK: New
/** /**
* Lengthen the `TimePeriod` by a `TimeInterval` * Lengthen the `TimePeriod` by a `TimeInterval`
* *
@ -583,10 +562,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end timePeriod.end = self.end
break break
} }
return timePeriod return timePeriod
} }
/** /**
* Lengthen the `TimePeriod` by a `TimeChunk` * Lengthen the `TimePeriod` by a `TimeChunk`
* *
@ -610,10 +589,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end timePeriod.end = end
break break
} }
return timePeriod return timePeriod
} }
/** /**
* Shorten the `TimePeriod` by a `TimeInterval` * Shorten the `TimePeriod` by a `TimeInterval`
* *
@ -638,10 +617,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end timePeriod.end = end
break break
} }
return timePeriod return timePeriod
} }
/** /**
* Shorten the `TimePeriod` by a `TimeChunk` * Shorten the `TimePeriod` by a `TimeChunk`
* *
@ -665,20 +644,19 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end timePeriod.end = end
break break
} }
return timePeriod return timePeriod
} }
// MARK: - Operator Overloads // MARK: - Operator Overloads
/** /**
* Operator overload for checking if two `TimePeriod`s are equal * Operator overload for checking if two `TimePeriod`s are equal
*/ */
static func ==(leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool { static func ==(leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool {
return leftAddend.equals(rightAddend) return leftAddend.equals(rightAddend)
} }
// Default anchor = beginning // Default anchor = beginning
/** /**
* Operator overload for lengthening a `TimePeriod` by a `TimeInterval` * Operator overload for lengthening a `TimePeriod` by a `TimeInterval`
@ -686,14 +664,14 @@ open class TimePeriod: TimePeriodProtocol {
static func +(leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod { static func +(leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning) return leftAddend.lengthened(by: rightAddend, at: .beginning)
} }
/** /**
* Operator overload for lengthening a `TimePeriod` by a `TimeChunk` * Operator overload for lengthening a `TimePeriod` by a `TimeChunk`
*/ */
static func +(leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod { static func +(leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning) return leftAddend.lengthened(by: rightAddend, at: .beginning)
} }
// Default anchor = beginning // Default anchor = beginning
/** /**
* Operator overload for shortening a `TimePeriod` by a `TimeInterval` * Operator overload for shortening a `TimePeriod` by a `TimeInterval`
@ -701,14 +679,14 @@ open class TimePeriod: TimePeriodProtocol {
static func -(minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod { static func -(minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning) return minuend.shortened(by: subtrahend, at: .beginning)
} }
/** /**
* Operator overload for shortening a `TimePeriod` by a `TimeChunk` * Operator overload for shortening a `TimePeriod` by a `TimeChunk`
*/ */
static func -(minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod { static func -(minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning) return minuend.shortened(by: subtrahend, at: .beginning)
} }
/** /**
* Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol` * Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol`
*/ */

64
Clocker/Dependencies/Date Additions/TimePeriodChain.swift

@ -18,9 +18,9 @@ import Foundation
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information.
*/ */
open class TimePeriodChain: TimePeriodGroup { open class TimePeriodChain: TimePeriodGroup {
// MARK: - Chain Existence Manipulation // MARK: - Chain Existence Manipulation
/** /**
* Append a TimePeriodProtocol to the periods array and update the Chain's * Append a TimePeriodProtocol to the periods array and update the Chain's
* beginning and end. * beginning and end.
@ -29,20 +29,19 @@ open class TimePeriodChain: TimePeriodGroup {
*/ */
public func append(_ period: TimePeriodProtocol) { public func append(_ period: TimePeriodProtocol) {
let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod) self.periods.append(newPeriod)
//Update updateExtremes //Update updateExtremes
if periods.count == 1 { if periods.count == 1 {
_beginning = period.beginning _beginning = period.beginning
_end = period.end _end = period.end
} } else {
else {
_end = _end?.addingTimeInterval(period.duration) _end = _end?.addingTimeInterval(period.duration)
} }
} }
/** /**
* Append a TimePeriodProtocol array to the periods array and update the Chain's * Append a TimePeriodProtocol array to the periods array and update the Chain's
* beginning and end. * beginning and end.
@ -52,21 +51,20 @@ open class TimePeriodChain: TimePeriodGroup {
public func append<G: TimePeriodGroup>(contentsOf group: G) { public func append<G: TimePeriodGroup>(contentsOf group: G) {
for period in group.periods { for period in group.periods {
let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod) self.periods.append(newPeriod)
//Update updateExtremes //Update updateExtremes
if periods.count == 1 { if periods.count == 1 {
_beginning = period.beginning _beginning = period.beginning
_end = period.end _end = period.end
} } else {
else {
_end = _end?.addingTimeInterval(period.duration) _end = _end?.addingTimeInterval(period.duration)
} }
} }
} }
/** /**
* Insert period into periods array at given index. * Insert period into periods array at given index.
* *
@ -78,16 +76,14 @@ open class TimePeriodChain: TimePeriodGroup {
if index == 0 && period.beginning != nil && period.end != nil { if index == 0 && period.beginning != nil && period.end != nil {
//Insert new period //Insert new period
periods.insert(period, at: index) periods.insert(period, at: index)
} } else if period.beginning != nil && period.end != nil {
else if period.beginning != nil && period.end != nil {
//Insert new period //Insert new period
periods.insert(period, at: index) periods.insert(period, at: index)
} } else {
else {
print("All TimePeriods in a TimePeriodChain must contain a defined start and end date") print("All TimePeriods in a TimePeriodChain must contain a defined start and end date")
return return
} }
//Shift all periods after inserted period //Shift all periods after inserted period
for i in 0..<periods.count { for i in 0..<periods.count {
if i > index && i > 0 { if i > index && i > 0 {
@ -96,10 +92,10 @@ open class TimePeriodChain: TimePeriodGroup {
periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration) periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration)
} }
} }
updateExtremes() updateExtremes()
} }
/** /**
* Remove from period array at the given index. * Remove from period array at the given index.
* *
@ -108,17 +104,17 @@ open class TimePeriodChain: TimePeriodGroup {
public func remove(at index: Int) { public func remove(at index: Int) {
//Retrieve duration of period to be removed //Retrieve duration of period to be removed
let duration = periods[index].duration let duration = periods[index].duration
//Remove period //Remove period
periods.remove(at: index) periods.remove(at: index)
//Shift all periods after inserted period //Shift all periods after inserted period
for i in index..<periods.count { for i in index..<periods.count {
periods[i].shift(by: -duration) periods[i].shift(by: -duration)
} }
updateExtremes() updateExtremes()
} }
/** /**
* Remove all periods from period array. * Remove all periods from period array.
*/ */
@ -126,9 +122,9 @@ open class TimePeriodChain: TimePeriodGroup {
self.periods.removeAll() self.periods.removeAll()
updateExtremes() updateExtremes()
} }
//MARK: - Chain Content Manipulation // MARK: - Chain Content Manipulation
/** /**
* In place, shifts all chain time periods by a given time interval * In place, shifts all chain time periods by a given time interval
* *
@ -138,23 +134,23 @@ open class TimePeriodChain: TimePeriodGroup {
for var period in self.periods { for var period in self.periods {
period.shift(by:duration) period.shift(by:duration)
} }
_beginning = _beginning?.addingTimeInterval(duration) _beginning = _beginning?.addingTimeInterval(duration)
_end = _end?.addingTimeInterval(duration) _end = _end?.addingTimeInterval(duration)
} }
public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform) return try periods.map(transform)
} }
public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded) return try periods.filter(isIncluded)
} }
internal override func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { internal override func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult) return try periods.reduce(initialResult, nextPartialResult)
} }
/** /**
* Removes the last object from the `TimePeriodChain` and returns it * Removes the last object from the `TimePeriodChain` and returns it
* *
@ -162,17 +158,17 @@ open class TimePeriodChain: TimePeriodGroup {
public func pop() -> TimePeriodProtocol? { public func pop() -> TimePeriodProtocol? {
let period = self.periods.popLast() let period = self.periods.popLast()
updateExtremes() updateExtremes()
return period return period
} }
internal func updateExtremes() { internal func updateExtremes() {
_beginning = periods.first?.beginning _beginning = periods.first?.beginning
_end = periods.last?.end _end = periods.last?.end
} }
// MARK: - Operator Overloads // MARK: - Operator Overloads
/** /**
* Operator overload for comparing `TimePeriodChain`s to each other * Operator overload for comparing `TimePeriodChain`s to each other
*/ */

56
Clocker/Dependencies/Date Additions/TimePeriodCollection.swift

@ -17,9 +17,9 @@ import Foundation
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information.
*/ */
open class TimePeriodCollection: TimePeriodGroup { open class TimePeriodCollection: TimePeriodGroup {
// MARK: - Collection Manipulation // MARK: - Collection Manipulation
/** /**
* Append a TimePeriodProtocol to the periods array and check if the Collection's * Append a TimePeriodProtocol to the periods array and check if the Collection's
* beginning and end should change. * beginning and end should change.
@ -30,7 +30,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.append(period) periods.append(period)
updateExtremes(period: period) updateExtremes(period: period)
} }
/** /**
* Append a TimePeriodProtocol array to the periods array and check if the Collection's * Append a TimePeriodProtocol array to the periods array and check if the Collection's
* beginning and end should change. * beginning and end should change.
@ -43,7 +43,7 @@ open class TimePeriodCollection: TimePeriodGroup {
updateExtremes(period: period) updateExtremes(period: period)
} }
} }
/** /**
* Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's * Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's
* beginning and end should change. * beginning and end should change.
@ -56,7 +56,7 @@ open class TimePeriodCollection: TimePeriodGroup {
updateExtremes(period: period) updateExtremes(period: period)
} }
} }
/** /**
* Insert period into periods array at given index. * Insert period into periods array at given index.
* *
@ -67,7 +67,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.insert(newElement, at: index) periods.insert(newElement, at: index)
updateExtremes(period: newElement) updateExtremes(period: newElement)
} }
/** /**
* Remove from period array at the given index. * Remove from period array at the given index.
* *
@ -77,7 +77,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.remove(at: at) periods.remove(at: at)
updateExtremes() updateExtremes()
} }
/** /**
* Remove all periods from period array. * Remove all periods from period array.
*/ */
@ -85,10 +85,9 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.removeAll() periods.removeAll()
updateExtremes() updateExtremes()
} }
// MARK: - Sorting // MARK: - Sorting
// In place // In place
/** /**
* Sort periods array in place by beginning * Sort periods array in place by beginning
@ -106,14 +105,14 @@ open class TimePeriodCollection: TimePeriodGroup {
} }
} }
} }
/** /**
* Sort periods array in place * Sort periods array in place
*/ */
public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) { public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
self.periods.sort(by: areInIncreasingOrder) self.periods.sort(by: areInIncreasingOrder)
} }
// New collection // New collection
/** /**
* Return collection with sorted periods array by beginning * Return collection with sorted periods array by beginning
@ -136,7 +135,7 @@ open class TimePeriodCollection: TimePeriodGroup {
collection.append(array) collection.append(array)
return collection return collection
} }
/** /**
* Return collection with sorted periods array * Return collection with sorted periods array
* *
@ -147,10 +146,9 @@ open class TimePeriodCollection: TimePeriodGroup {
collection.append(self.periods.sorted(by: areInIncreasingOrder)) collection.append(self.periods.sorted(by: areInIncreasingOrder))
return collection return collection
} }
// MARK: - Collection Relationship // MARK: - Collection Relationship
// Potentially use .reduce() instead of these functions // Potentially use .reduce() instead of these functions
/** /**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
@ -168,7 +166,7 @@ open class TimePeriodCollection: TimePeriodGroup {
}) })
return collection return collection
} }
/** /**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing
* the given date. * the given date.
@ -185,7 +183,7 @@ open class TimePeriodCollection: TimePeriodGroup {
}) })
return collection return collection
} }
/** /**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
* containing either the start date or the end date--or both--of the given `TimePeriod`. * containing either the start date or the end date--or both--of the given `TimePeriod`.
@ -202,9 +200,9 @@ open class TimePeriodCollection: TimePeriodGroup {
}) })
return collection return collection
} }
// MARK: - Map // MARK: - Map
public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection { public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection {
var mappedArray = [TimePeriodProtocol]() var mappedArray = [TimePeriodProtocol]()
mappedArray = try periods.map(transform) mappedArray = try periods.map(transform)
@ -215,18 +213,18 @@ open class TimePeriodCollection: TimePeriodGroup {
} }
return mappedCollection return mappedCollection
} }
// MARK: - Operator Overloads // MARK: - Operator Overloads
/** /**
* Operator overload for comparing `TimePeriodCollection`s to each other * Operator overload for comparing `TimePeriodCollection`s to each other
*/ */
public static func ==(left: TimePeriodCollection, right: TimePeriodCollection) -> Bool { public static func ==(left: TimePeriodCollection, right: TimePeriodCollection) -> Bool {
return left.equals(right) return left.equals(right)
} }
//MARK: - Helpers // MARK: - Helpers
internal func updateExtremes(period: TimePeriodProtocol) { internal func updateExtremes(period: TimePeriodProtocol) {
//Check incoming period against previous beginning and end date //Check incoming period against previous beginning and end date
if self.count == 1 { if self.count == 1 {
@ -236,9 +234,9 @@ open class TimePeriodCollection: TimePeriodGroup {
_beginning = nilOrEarlier(date1: _beginning, date2: period.beginning) _beginning = nilOrEarlier(date1: _beginning, date2: period.beginning)
_end = nilOrLater(date1: _end, date2: period.end) _end = nilOrLater(date1: _end, date2: period.end)
} }
} }
internal func updateExtremes() { internal func updateExtremes() {
if periods.count == 0 { if periods.count == 0 {
_beginning = nil _beginning = nil
@ -252,7 +250,7 @@ open class TimePeriodCollection: TimePeriodGroup {
} }
} }
} }
internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? { internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil { if date1 == nil || date2 == nil {
return nil return nil
@ -260,7 +258,7 @@ open class TimePeriodCollection: TimePeriodGroup {
return date1!.earlierDate(date2!) return date1!.earlierDate(date2!)
} }
} }
internal func nilOrLater(date1: Date?, date2: Date?) -> Date? { internal func nilOrLater(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil { if date1 == nil || date2 == nil {
return nil return nil

43
Clocker/Dependencies/Date Additions/TimePeriodGroup.swift

@ -16,17 +16,17 @@ import Foundation
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information.
*/ */
open class TimePeriodGroup: Sequence { open class TimePeriodGroup: Sequence {
// MARK: - Variables // MARK: - Variables
/** /**
* The array of periods that define the group. * The array of periods that define the group.
*/ */
internal var periods: [TimePeriodProtocol] = [] internal var periods: [TimePeriodProtocol] = []
internal var _beginning: Date? internal var _beginning: Date?
internal var _end: Date? internal var _end: Date?
/** /**
* The earliest beginning date of a `TimePeriod` in the group. * The earliest beginning date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil beginning date (indefinite). * Nil if any `TimePeriod` in group has a nil beginning date (indefinite).
@ -35,7 +35,7 @@ open class TimePeriodGroup: Sequence {
public var beginning: Date? { public var beginning: Date? {
return _beginning return _beginning
} }
/** /**
* The latest end date of a `TimePeriod` in the group. * The latest end date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil end date (indefinite). * Nil if any `TimePeriod` in group has a nil end date (indefinite).
@ -44,14 +44,14 @@ open class TimePeriodGroup: Sequence {
public var end: Date? { public var end: Date? {
return _end return _end
} }
/** /**
* The number of periods in the periods array. * The number of periods in the periods array.
*/ */
public var count: Int { public var count: Int {
return periods.count return periods.count
} }
/** /**
* The total amount of time between the earliest and latest dates stored in the * The total amount of time between the earliest and latest dates stored in the
* periods array. Nil if any beginning or end date in any contained period is nil. * periods array. Nil if any beginning or end date in any contained period is nil.
@ -62,15 +62,15 @@ open class TimePeriodGroup: Sequence {
} }
return nil return nil
} }
// MARK: - Initializers // MARK: - Initializers
public init() { public init() {
} }
// MARK: - Comparisons // MARK: - Comparisons
/** /**
* If `self.periods` contains the exact elements as the given group's periods array. * If `self.periods` contains the exact elements as the given group's periods array.
* *
@ -81,45 +81,44 @@ open class TimePeriodGroup: Sequence {
public func equals(_ group: TimePeriodGroup) -> Bool { public func equals(_ group: TimePeriodGroup) -> Bool {
return containSameElements(array1: self.periods, group.periods) return containSameElements(array1: self.periods, group.periods)
} }
// MARK: - Sequence Protocol // MARK: - Sequence Protocol
public func makeIterator() -> IndexingIterator<Array<TimePeriodProtocol>> { public func makeIterator() -> IndexingIterator<Array<TimePeriodProtocol>> {
return periods.makeIterator() return periods.makeIterator()
} }
public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform) return try periods.map(transform)
} }
public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded) return try periods.filter(isIncluded)
} }
public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows { public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows {
return try periods.forEach(body) return try periods.forEach(body)
} }
public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence<TimePeriodProtocol>] { public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence<TimePeriodProtocol>] {
return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init) return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init)
} }
subscript(index: Int) -> TimePeriodProtocol { subscript(index: Int) -> TimePeriodProtocol {
get { get {
return periods[index] return periods[index]
} }
} }
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult) return try periods.reduce(initialResult, nextPartialResult)
} }
internal func containSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool { internal func containSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool {
guard array1.count == array2.count else { guard array1.count == array2.count else {
return false // No need to sorting if they already have different counts return false // No need to sorting if they already have different counts
} }
var compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in var compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil { if period1.beginning == nil && period2.beginning == nil {
return false return false

99
Clocker/Dependencies/Solar.swift

@ -4,13 +4,13 @@ import Cocoa
import CoreLocation import CoreLocation
public struct Solar { public struct Solar {
/// The coordinate that is used for the calculation /// The coordinate that is used for the calculation
public let coordinate: CLLocationCoordinate2D public let coordinate: CLLocationCoordinate2D
/// The date to generate sunrise / sunset times for /// The date to generate sunrise / sunset times for
public fileprivate(set) var date: Date public fileprivate(set) var date: Date
public fileprivate(set) var sunrise: Date? public fileprivate(set) var sunrise: Date?
public fileprivate(set) var sunset: Date? public fileprivate(set) var sunset: Date?
public fileprivate(set) var civilSunrise: Date? public fileprivate(set) var civilSunrise: Date?
@ -19,24 +19,24 @@ public struct Solar {
public fileprivate(set) var nauticalSunset: Date? public fileprivate(set) var nauticalSunset: Date?
public fileprivate(set) var astronomicalSunrise: Date? public fileprivate(set) var astronomicalSunrise: Date?
public fileprivate(set) var astronomicalSunset: Date? public fileprivate(set) var astronomicalSunset: Date?
// MARK: Init // MARK: Init
public init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) { public init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) {
self.date = date self.date = date
guard CLLocationCoordinate2DIsValid(coordinate) else { guard CLLocationCoordinate2DIsValid(coordinate) else {
return nil return nil
} }
self.coordinate = coordinate self.coordinate = coordinate
// Fill this Solar object with relevant data // Fill this Solar object with relevant data
calculate() calculate()
} }
// MARK: - Public functions // MARK: - Public functions
/// Sets all of the Solar object's sunrise / sunset variables, if possible. /// Sets all of the Solar object's sunrise / sunset variables, if possible.
/// - Note: Can return `nil` objects if sunrise / sunset does not occur on that day. /// - Note: Can return `nil` objects if sunrise / sunset does not occur on that day.
public mutating func calculate() { public mutating func calculate() {
@ -49,14 +49,14 @@ public struct Solar {
astronomicalSunrise = calculate(.sunrise, for: date, and: .astronimical) astronomicalSunrise = calculate(.sunrise, for: date, and: .astronimical)
astronomicalSunset = calculate(.sunset, for: date, and: .astronimical) astronomicalSunset = calculate(.sunset, for: date, and: .astronimical)
} }
// MARK: - Private functions // MARK: - Private functions
fileprivate enum SunriseSunset { fileprivate enum SunriseSunset {
case sunrise case sunrise
case sunset case sunset
} }
/// Used for generating several of the possible sunrise / sunset times /// Used for generating several of the possible sunrise / sunset times
fileprivate enum Zenith: Double { fileprivate enum Zenith: Double {
case official = 90.83 case official = 90.83
@ -64,85 +64,85 @@ public struct Solar {
case nautical = 102 case nautical = 102
case astronimical = 108 case astronimical = 108
} }
fileprivate func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? { fileprivate func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? {
guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil } guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil }
// Get the day of the year // Get the day of the year
var calendar = Calendar(identifier: .gregorian) var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = utcTimezone calendar.timeZone = utcTimezone
guard let dayInt = calendar.ordinality(of: .day, in: .year, for: date) else { return nil } guard let dayInt = calendar.ordinality(of: .day, in: .year, for: date) else { return nil }
let day = Double(dayInt) let day = Double(dayInt)
// Convert longitude to hour value and calculate an approx. time // Convert longitude to hour value and calculate an approx. time
let lngHour = coordinate.longitude / 15 let lngHour = coordinate.longitude / 15
let hourTime: Double = sunriseSunset == .sunrise ? 6 : 18 let hourTime: Double = sunriseSunset == .sunrise ? 6 : 18
let t = day + ((hourTime - lngHour) / 24) let t = day + ((hourTime - lngHour) / 24)
// Calculate the suns mean anomaly // Calculate the suns mean anomaly
let M = (0.9856 * t) - 3.289 let M = (0.9856 * t) - 3.289
// Calculate the sun's true longitude // Calculate the sun's true longitude
let subexpression1 = 1.916 * sin(M.degreesToRadians) let subexpression1 = 1.916 * sin(M.degreesToRadians)
let subexpression2 = 0.020 * sin(2 * M.degreesToRadians) let subexpression2 = 0.020 * sin(2 * M.degreesToRadians)
var L = M + subexpression1 + subexpression2 + 282.634 var L = M + subexpression1 + subexpression2 + 282.634
// Normalise L into [0, 360] range // Normalise L into [0, 360] range
L = normalise(L, withMaximum: 360) L = normalise(L, withMaximum: 360)
// Calculate the Sun's right ascension // Calculate the Sun's right ascension
var RA = atan(0.91764 * tan(L.degreesToRadians)).radiansToDegrees var RA = atan(0.91764 * tan(L.degreesToRadians)).radiansToDegrees
// Normalise RA into [0, 360] range // Normalise RA into [0, 360] range
RA = normalise(RA, withMaximum: 360) RA = normalise(RA, withMaximum: 360)
// Right ascension value needs to be in the same quadrant as L... // Right ascension value needs to be in the same quadrant as L...
let Lquadrant = floor(L / 90) * 90 let Lquadrant = floor(L / 90) * 90
let RAquadrant = floor(RA / 90) * 90 let RAquadrant = floor(RA / 90) * 90
RA = RA + (Lquadrant - RAquadrant) RA = RA + (Lquadrant - RAquadrant)
// Convert RA into hours // Convert RA into hours
RA = RA / 15 RA = RA / 15
// Calculate Sun's declination // Calculate Sun's declination
let sinDec = 0.39782 * sin(L.degreesToRadians) let sinDec = 0.39782 * sin(L.degreesToRadians)
let cosDec = cos(asin(sinDec)) let cosDec = cos(asin(sinDec))
// Calculate the Sun's local hour angle // Calculate the Sun's local hour angle
let cosH = (cos(zenith.rawValue.degreesToRadians) - (sinDec * sin(coordinate.latitude.degreesToRadians))) / (cosDec * cos(coordinate.latitude.degreesToRadians)) let cosH = (cos(zenith.rawValue.degreesToRadians) - (sinDec * sin(coordinate.latitude.degreesToRadians))) / (cosDec * cos(coordinate.latitude.degreesToRadians))
// No sunrise // No sunrise
guard cosH < 1 else { guard cosH < 1 else {
return nil return nil
} }
// No sunset // No sunset
guard cosH > -1 else { guard cosH > -1 else {
return nil return nil
} }
// Finish calculating H and convert into hours // Finish calculating H and convert into hours
let tempH = sunriseSunset == .sunrise ? 360 - acos(cosH).radiansToDegrees : acos(cosH).radiansToDegrees let tempH = sunriseSunset == .sunrise ? 360 - acos(cosH).radiansToDegrees : acos(cosH).radiansToDegrees
let H = tempH / 15.0 let H = tempH / 15.0
// Calculate local mean time of rising // Calculate local mean time of rising
let T = H + RA - (0.06571 * t) - 6.622 let T = H + RA - (0.06571 * t) - 6.622
// Adjust time back to UTC // Adjust time back to UTC
var UT = T - lngHour var UT = T - lngHour
// Normalise UT into [0, 24] range // Normalise UT into [0, 24] range
UT = normalise(UT, withMaximum: 24) UT = normalise(UT, withMaximum: 24)
// Calculate all of the sunrise's / sunset's date components // Calculate all of the sunrise's / sunset's date components
let hour = floor(UT) let hour = floor(UT)
let minute = floor((UT - hour) * 60.0) let minute = floor((UT - hour) * 60.0)
let second = (((UT - hour) * 60) - minute) * 60.0 let second = (((UT - hour) * 60) - minute) * 60.0
let shouldBeYesterday = lngHour > 0 && UT > 12 && sunriseSunset == .sunrise let shouldBeYesterday = lngHour > 0 && UT > 12 && sunriseSunset == .sunrise
let shouldBeTomorrow = lngHour < 0 && UT < 12 && sunriseSunset == .sunset let shouldBeTomorrow = lngHour < 0 && UT < 12 && sunriseSunset == .sunset
let setDate: Date let setDate: Date
if shouldBeYesterday { if shouldBeYesterday {
setDate = Date(timeInterval: -(60 * 60 * 24), since: date) setDate = Date(timeInterval: -(60 * 60 * 24), since: date)
@ -151,35 +151,35 @@ public struct Solar {
} else { } else {
setDate = date setDate = date
} }
var components = calendar.dateComponents([.day, .month, .year], from: setDate) var components = calendar.dateComponents([.day, .month, .year], from: setDate)
components.hour = Int(hour) components.hour = Int(hour)
components.minute = Int(minute) components.minute = Int(minute)
components.second = Int(second) components.second = Int(second)
calendar.timeZone = utcTimezone calendar.timeZone = utcTimezone
return calendar.date(from: components) return calendar.date(from: components)
} }
/// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum` /// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum`
fileprivate func normalise(_ value: Double, withMaximum maximum: Double) -> Double { fileprivate func normalise(_ value: Double, withMaximum maximum: Double) -> Double {
var value = value var value = value
if value < 0 { if value < 0 {
value += maximum value += maximum
} }
if value > maximum { if value > maximum {
value -= maximum value -= maximum
} }
return value return value
} }
} }
extension Solar { extension Solar {
/// Whether the location specified by the `latitude` and `longitude` is in daytime on `date` /// Whether the location specified by the `latitude` and `longitude` is in daytime on `date`
/// - Complexity: O(1) /// - Complexity: O(1)
public var isDaytime: Bool { public var isDaytime: Bool {
@ -189,23 +189,23 @@ extension Solar {
else { else {
return false return false
} }
let beginningOfDay = sunrise.timeIntervalSince1970 let beginningOfDay = sunrise.timeIntervalSince1970
let endOfDay = sunset.timeIntervalSince1970 let endOfDay = sunset.timeIntervalSince1970
let currentTime = self.date.timeIntervalSince1970 let currentTime = self.date.timeIntervalSince1970
let isSunriseOrLater = currentTime >= beginningOfDay let isSunriseOrLater = currentTime >= beginningOfDay
let isBeforeSunset = currentTime < endOfDay let isBeforeSunset = currentTime < endOfDay
return isSunriseOrLater && isBeforeSunset return isSunriseOrLater && isBeforeSunset
} }
/// Whether the location specified by the `latitude` and `longitude` is in nighttime on `date` /// Whether the location specified by the `latitude` and `longitude` is in nighttime on `date`
/// - Complexity: O(1) /// - Complexity: O(1)
public var isNighttime: Bool { public var isNighttime: Bool {
return !isDaytime return !isDaytime
} }
} }
// MARK: - Helper extensions // MARK: - Helper extensions
@ -214,9 +214,8 @@ private extension Double {
var degreesToRadians: Double { var degreesToRadians: Double {
return Double(self) * (Double.pi / 180.0) return Double(self) * (Double.pi / 180.0)
} }
var radiansToDegrees: Double { var radiansToDegrees: Double {
return (Double(self) * 180.0) / Double.pi return (Double(self) * 180.0) / Double.pi
} }
} }

12
Clocker/Events and Reminders/CalendarHandler.swift

@ -116,7 +116,7 @@ extension EventCenter {
if let `self` = self, entity == .event, granted { if let `self` = self, entity == .event, granted {
self.saveDefaultIdentifiersList() self.saveDefaultIdentifiersList()
} }
completionHandler(granted) completionHandler(granted)
} }
} }
@ -146,12 +146,12 @@ extension EventCenter {
print("Unable to filter events because user hasn't selected calendars") print("Unable to filter events because user hasn't selected calendars")
} }
func saveDefaultIdentifiersList() { func saveDefaultIdentifiersList() {
OperationQueue.main.addOperation { [weak self] in OperationQueue.main.addOperation { [weak self] in
guard let `self` = self else { return } guard let `self` = self else { return }
let allCalendars = self.retrieveAllCalendarIdentifiers() let allCalendars = self.retrieveAllCalendarIdentifiers()
if !allCalendars.isEmpty { if !allCalendars.isEmpty {
UserDefaults.standard.set(allCalendars, forKey: CLSelectedCalendars) UserDefaults.standard.set(allCalendars, forKey: CLSelectedCalendars)
print("Finished saving all calendar identifiers in default") print("Finished saving all calendar identifiers in default")
@ -165,7 +165,7 @@ extension EventCenter {
return calendar.calendarIdentifier return calendar.calendarIdentifier
} }
} }
func fetchEvents(_ start: Int, _ end: Int) { func fetchEvents(_ start: Int, _ end: Int) {
if calendarAccessDenied() || calendarAccessNotDetermined() { if calendarAccessDenied() || calendarAccessNotDetermined() {
print("Refetching aborted because we don't have permission!") print("Refetching aborted because we don't have permission!")
@ -258,9 +258,7 @@ extension EventCenter {
for date in eventsForDateMapper.keys { for date in eventsForDateMapper.keys {
let sortedEvents = eventsForDateMapper[date]?.sorted(by: { (e1, e2) -> Bool in let sortedEvents = eventsForDateMapper[date]?.sorted(by: { (e1, e2) -> Bool in
if e1.isAllDay { return true } if e1.isAllDay { return true } else if e2.isAllDay { return false } else { return e1.event.startDate < e2.event.startDate }
else if e2.isAllDay { return false }
else { return e1.event.startDate < e2.event.startDate }
}) })
eventsForDateMapper[date] = sortedEvents eventsForDateMapper[date] = sortedEvents
} }

24
Clocker/Menu Bar/MenubarHandler.swift

@ -5,20 +5,20 @@ import EventKit
class MenubarHandler: NSObject { class MenubarHandler: NSObject {
@objc func titleForMenubar() -> String? { @objc func titleForMenubar() -> String? {
if let nextEvent = checkForUpcomingEvents() { if let nextEvent = checkForUpcomingEvents() {
return nextEvent return nextEvent
} }
guard let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] else { guard let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] else {
return nil return nil
} }
// If the menubar is in compact mode, we don't need any of the below calculations; exit early // If the menubar is in compact mode, we don't need any of the below calculations; exit early
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if DataStore.shared().shouldDisplay(.menubarCompactMode) {
return nil return nil
} }
if menubarTitles.count > 0 { if menubarTitles.count > 0 {
let titles = menubarTitles.map({ (data) -> String? in let titles = menubarTitles.map({ (data) -> String? in
let timezone = TimezoneData.customObject(from: data) let timezone = TimezoneData.customObject(from: data)
@ -34,7 +34,7 @@ class MenubarHandler: NSObject {
} }
private func checkForUpcomingEvents() -> String? { private func checkForUpcomingEvents() -> String? {
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
let filteredDates = EventCenter.sharedCenter().eventsForDate let filteredDates = EventCenter.sharedCenter().eventsForDate
@ -42,20 +42,20 @@ class MenubarHandler: NSObject {
guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else { guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else {
return nil return nil
} }
for event in events { for event in events {
if event.event.startDate.timeIntervalSinceNow > 0 && !event.isAllDay { if event.event.startDate.timeIntervalSinceNow > 0 && !event.isAllDay {
let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60 let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60
if timeForEventToStart > 30 { if timeForEventToStart > 30 {
print("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") print("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins")
continue continue
} }
return EventCenter.sharedCenter().format(event: event.event) return EventCenter.sharedCenter().format(event: event.event)
} }
} }

66
Clocker/Menu Bar/StatusContainerView.swift

@ -4,42 +4,42 @@ import Cocoa
func bufferCalculatedWidth() -> Int { func bufferCalculatedWidth() -> Int {
var totalWidth = 55 var totalWidth = 55
if DataStore.shared().shouldShowDateInMenubar() { if DataStore.shared().shouldShowDateInMenubar() {
totalWidth += 8 totalWidth += 8
} }
if DataStore.shared().shouldDisplay(.twelveHour) { if DataStore.shared().shouldDisplay(.twelveHour) {
totalWidth += 18 totalWidth += 18
} }
if DataStore.shared().shouldDisplay(.seconds) { if DataStore.shared().shouldDisplay(.seconds) {
totalWidth += 15 totalWidth += 15
} }
return totalWidth return totalWidth
} }
func compactWidth(for timezone: TimezoneData) -> Int { func compactWidth(for timezone: TimezoneData) -> Int {
var totalWidth = 55 var totalWidth = 55
let timeFormat = timezone.timezoneFormat() let timeFormat = timezone.timezoneFormat()
if DataStore.shared().shouldShowDateInMenubar() { if DataStore.shared().shouldShowDateInMenubar() {
totalWidth += 8 totalWidth += 8
} }
if timeFormat == DateFormat.twelveHour || timeFormat == DateFormat.twelveHourWithSeconds { if timeFormat == DateFormat.twelveHour || timeFormat == DateFormat.twelveHourWithSeconds {
totalWidth += 18 totalWidth += 18
} else if timeFormat == DateFormat.twentyFourHour || timeFormat == DateFormat.twentyFourHourWithSeconds { } else if timeFormat == DateFormat.twentyFourHour || timeFormat == DateFormat.twentyFourHourWithSeconds {
totalWidth += 0 totalWidth += 0
} }
if timezone.shouldShowSeconds() { if timezone.shouldShowSeconds() {
// Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM // Slight buffer needed when the Menubar supplementary text was Mon 9:27:58 AM
totalWidth += 15 totalWidth += 15
} }
return totalWidth return totalWidth
} }
@ -47,17 +47,17 @@ func compactWidth(for timezone: TimezoneData) -> Int {
let bufferWidth: CGFloat = 9.5 let bufferWidth: CGFloat = 9.5
class StatusContainerView: NSView { class StatusContainerView: NSView {
private var previousX: Int = 0 private var previousX: Int = 0
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
wantsLayer = true wantsLayer = true
layer?.backgroundColor = NSColor.clear.cgColor layer?.backgroundColor = NSColor.clear.cgColor
} }
init(with timezones: [Data]) { init(with timezones: [Data]) {
func addSubviews() { func addSubviews() {
timezones.forEach { timezones.forEach {
if let timezoneObject = TimezoneData.customObject(from: $0) { if let timezoneObject = TimezoneData.customObject(from: $0) {
@ -65,69 +65,69 @@ class StatusContainerView: NSView {
} }
} }
} }
func containerWidth(for timezones: [Data]) -> CGFloat { func containerWidth(for timezones: [Data]) -> CGFloat {
let compressedWidth = timezones.reduce(0.0) { (result, timezone) -> CGFloat in let compressedWidth = timezones.reduce(0.0) { (result, timezone) -> CGFloat in
if let timezoneObject = TimezoneData.customObject(from: timezone) { if let timezoneObject = TimezoneData.customObject(from: timezone) {
let precalculatedWidth = Double(compactWidth(for: timezoneObject)) let precalculatedWidth = Double(compactWidth(for: timezoneObject))
let operationObject = TimezoneDataOperations(with: timezoneObject) let operationObject = TimezoneDataOperations(with: timezoneObject)
let calculatedSize = compactModeTimeFont.size(operationObject.compactMenuHeader(), precalculatedWidth, attributes: timeAttributes) let calculatedSize = compactModeTimeFont.size(operationObject.compactMenuHeader(), precalculatedWidth, attributes: timeAttributes)
return result + calculatedSize.width + bufferWidth return result + calculatedSize.width + bufferWidth
} }
return result + CGFloat(bufferCalculatedWidth()) return result + CGFloat(bufferCalculatedWidth())
} }
let calculatedWidth = min(compressedWidth, let calculatedWidth = min(compressedWidth,
CGFloat(timezones.count * bufferCalculatedWidth())) CGFloat(timezones.count * bufferCalculatedWidth()))
return calculatedWidth return calculatedWidth
} }
let statusItemWidth = containerWidth(for: timezones) let statusItemWidth = containerWidth(for: timezones)
let frame = NSRect(x: 0, y: 0, width: statusItemWidth, height: 30) let frame = NSRect(x: 0, y: 0, width: statusItemWidth, height: 30)
super.init(frame: frame) super.init(frame: frame)
addSubviews() addSubviews()
} }
required init?(coder decoder: NSCoder) { required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func addTimezone(_ timezone: TimezoneData) { func addTimezone(_ timezone: TimezoneData) {
let calculatedWidth = bestWidth(for: timezone) let calculatedWidth = bestWidth(for: timezone)
let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30)
let statusItemView = StatusItemView(frame: frame) let statusItemView = StatusItemView(frame: frame)
statusItemView.dataObject = timezone statusItemView.dataObject = timezone
addSubview(statusItemView) addSubview(statusItemView)
previousX += calculatedWidth previousX += calculatedWidth
} }
private func bestWidth(for timezone: TimezoneData) -> Int { private func bestWidth(for timezone: TimezoneData) -> Int {
let operation = TimezoneDataOperations(with: timezone) let operation = TimezoneDataOperations(with: timezone)
let bestSize = compactModeTimeFont.size(operation.compactMenuHeader(), Double(compactWidth(for: timezone)), attributes: timeAttributes) let bestSize = compactModeTimeFont.size(operation.compactMenuHeader(), Double(compactWidth(for: timezone)), attributes: timeAttributes)
return Int(bestSize.width + bufferWidth) return Int(bestSize.width + bufferWidth)
} }
func updateTime() { func updateTime() {
if subviews.isEmpty { if subviews.isEmpty {
assertionFailure("Subviews count should > 0") assertionFailure("Subviews count should > 0")
} }
subviews.forEach { subviews.forEach {
if let statusItem = $0 as? StatusItemView { if let statusItem = $0 as? StatusItemView {
statusItem.updateTimeInMenubar() statusItem.updateTimeInMenubar()
} }
} }
} }
} }

24
Clocker/Menu Bar/StatusItemHandler.swift

@ -11,9 +11,9 @@ private enum MenubarState {
class StatusItemHandler: NSObject { class StatusItemHandler: NSObject {
var hasActiveIcon: Bool = false var hasActiveIcon: Bool = false
var menubarTimer: Timer? var menubarTimer: Timer?
var statusItem: NSStatusItem = { var statusItem: NSStatusItem = {
let s = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let s = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
s.highlightMode = false s.highlightMode = false
@ -25,9 +25,9 @@ class StatusItemHandler: NSObject {
private var parentView: StatusContainerView? private var parentView: StatusContainerView?
private var nsCalendar = Calendar.autoupdatingCurrent private var nsCalendar = Calendar.autoupdatingCurrent
private lazy var units: Set<Calendar.Component> = Set([.era, .year, .month, .day, .hour, .minute]) private lazy var units: Set<Calendar.Component> = Set([.era, .year, .month, .day, .hour, .minute])
// Current State is set twice when the user first launches an app. // Current State is set twice when the user first launches an app.
// First, when StatusItemHandler() is instantiated in AppDelegate // First, when StatusItemHandler() is instantiated in AppDelegate
// Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet. // Second, when AppDelegate.fetchLocalTimezone() is called triggering a customLabel didSet.
@ -44,7 +44,7 @@ class StatusItemHandler: NSObject {
case .icon: case .icon:
statusItem.button?.image = nil statusItem.button?.image = nil
} }
// Now setup for the new menubar state // Now setup for the new menubar state
switch currentState { switch currentState {
case .compactText: case .compactText:
@ -54,7 +54,7 @@ class StatusItemHandler: NSObject {
case .icon: case .icon:
setClockerIcon() setClockerIcon()
} }
print("\nStatus Bar Current State changed: \(currentState)\n") print("\nStatus Bar Current State changed: \(currentState)\n")
} }
} }
@ -120,7 +120,7 @@ class StatusItemHandler: NSObject {
parentView = nil parentView = nil
let menubarTimezones = retrieveSyncedMenubarTimezones() let menubarTimezones = retrieveSyncedMenubarTimezones()
if menubarTimezones.isEmpty { if menubarTimezones.isEmpty {
currentState = .icon currentState = .icon
return return
@ -130,10 +130,10 @@ class StatusItemHandler: NSObject {
statusItem.view = parentView statusItem.view = parentView
statusItem.view?.window?.backgroundColor = NSColor.clear statusItem.view?.window?.backgroundColor = NSColor.clear
} }
private func retrieveSyncedMenubarTimezones() -> [Data] { private func retrieveSyncedMenubarTimezones() -> [Data] {
let defaultPreferences = DataStore.shared().retrieve(key: CLDefaultPreferenceKey) as? [Data] ?? [] let defaultPreferences = DataStore.shared().retrieve(key: CLDefaultPreferenceKey) as? [Data] ?? []
let menubarTimezones = defaultPreferences.filter { (data) -> Bool in let menubarTimezones = defaultPreferences.filter { (data) -> Bool in
if let timezoneObj = TimezoneData.customObject(from: data) { if let timezoneObj = TimezoneData.customObject(from: data) {
return timezoneObj.isFavourite == 1 return timezoneObj.isFavourite == 1
@ -190,10 +190,10 @@ class StatusItemHandler: NSObject {
RunLoop.main.add(runLoopTimer, forMode: .common) RunLoop.main.add(runLoopTimer, forMode: .common)
} }
private func shouldDisplaySecondsInMenubar() -> Bool { private func shouldDisplaySecondsInMenubar() -> Bool {
let syncedTimezones = retrieveSyncedMenubarTimezones() let syncedTimezones = retrieveSyncedMenubarTimezones()
for timezone in syncedTimezones { for timezone in syncedTimezones {
if let timezoneObj = TimezoneData.customObject(from: timezone) { if let timezoneObj = TimezoneData.customObject(from: timezone) {
let shouldShowSeconds = timezoneObj.shouldShowSeconds() let shouldShowSeconds = timezoneObj.shouldShowSeconds()
@ -203,7 +203,7 @@ class StatusItemHandler: NSObject {
} }
continue continue
} }
return false return false
} }

31
Clocker/Menu Bar/StatusItemView.swift

@ -2,7 +2,6 @@
import Cocoa import Cocoa
private var defaultParagraphStyle: NSMutableParagraphStyle { private var defaultParagraphStyle: NSMutableParagraphStyle {
let p = NSMutableParagraphStyle() let p = NSMutableParagraphStyle()
p.alignment = .center p.alignment = .center
@ -15,20 +14,20 @@ var compactModeTimeFont: NSFont {
} }
var timeAttributes: [NSAttributedString.Key: AnyObject] { var timeAttributes: [NSAttributedString.Key: AnyObject] {
let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black
let attributes = [ let attributes = [
NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
] ]
return attributes return attributes
} }
class StatusItemView: NSView { class StatusItemView: NSView {
// MARK: Private variables // MARK: Private variables
private let locationView: NSTextField = NSTextField(labelWithString: "Hello") private let locationView: NSTextField = NSTextField(labelWithString: "Hello")
private let timeView: NSTextField = NSTextField(labelWithString: "Mon 19:14 PM") private let timeView: NSTextField = NSTextField(labelWithString: "Mon 19:14 PM")
@ -37,7 +36,7 @@ class StatusItemView: NSView {
} }
private var textFontAttributes: [NSAttributedString.Key: Any] { private var textFontAttributes: [NSAttributedString.Key: Any] {
let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black
let textFontAttributes = [ let textFontAttributes = [
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10),
NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.foregroundColor: textColor,
@ -46,7 +45,7 @@ class StatusItemView: NSView {
] ]
return textFontAttributes return textFontAttributes
} }
// MARK: Public // MARK: Public
var dataObject: TimezoneData! { var dataObject: TimezoneData! {
didSet { didSet {
@ -56,23 +55,23 @@ class StatusItemView: NSView {
override init(frame frameRect: NSRect) { override init(frame frameRect: NSRect) {
super.init(frame: frameRect) super.init(frame: frameRect)
[timeView, locationView].forEach { [timeView, locationView].forEach {
$0.wantsLayer = true $0.wantsLayer = true
$0.applyDefaultStyle() $0.applyDefaultStyle()
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0) addSubview($0)
} }
timeView.disableWrapping() timeView.disableWrapping()
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
locationView.leadingAnchor.constraint(equalTo: leadingAnchor), locationView.leadingAnchor.constraint(equalTo: leadingAnchor),
locationView.trailingAnchor.constraint(equalTo: trailingAnchor), locationView.trailingAnchor.constraint(equalTo: trailingAnchor),
locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7), locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7),
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35) locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35)
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
timeView.leadingAnchor.constraint(equalTo: leadingAnchor), timeView.leadingAnchor.constraint(equalTo: leadingAnchor),
timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
@ -80,26 +79,26 @@ class StatusItemView: NSView {
timeView.bottomAnchor.constraint(equalTo: bottomAnchor) timeView.bottomAnchor.constraint(equalTo: bottomAnchor)
]) ])
} }
func updateTimeInMenubar() { func updateTimeInMenubar() {
timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes)
} }
private func initialSetup() { private func initialSetup() {
locationView.attributedStringValue = NSAttributedString(string: dataObject.formattedTimezoneLabel(), attributes: textFontAttributes) locationView.attributedStringValue = NSAttributedString(string: dataObject.formattedTimezoneLabel(), attributes: textFontAttributes)
timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes)
} }
required init?(coder decoder: NSCoder) { required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func mouseDown(with event: NSEvent) { override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event) super.mouseDown(with: event)
guard let mainDelegate = NSApplication.shared.delegate as? AppDelegate else { guard let mainDelegate = NSApplication.shared.delegate as? AppDelegate else {
return return
} }
mainDelegate.togglePanel(event) mainDelegate.togglePanel(event)
} }
} }

2
Clocker/Onboarding/FinalOnboardingViewController.swift

@ -7,7 +7,7 @@ class FinalOnboardingViewController: NSViewController {
@IBOutlet var subtitleLabel: NSTextField! @IBOutlet var subtitleLabel: NSTextField!
@IBOutlet var accesoryLabel: NSTextField! @IBOutlet var accesoryLabel: NSTextField!
@IBOutlet var accessoryImageView: NSImageView! @IBOutlet var accessoryImageView: NSImageView!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
titleLabel.stringValue = "You're all set!" titleLabel.stringValue = "You're all set!"

2
Clocker/Onboarding/OnboardingController.swift

@ -20,7 +20,7 @@ class OnboardingController: NSWindowController {
super.showWindow(sender) super.showWindow(sender)
window?.center() window?.center()
} }
func launch() { func launch() {
showWindow(nil) showWindow(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)

32
Clocker/Onboarding/OnboardingParentViewController.swift

@ -60,14 +60,14 @@ class OnboardingParentViewController: NSViewController {
private func setupUI() { private func setupUI() {
setIdentifiersForTests() setIdentifiersForTests()
positiveButton.title = "Get Started" positiveButton.title = "Get Started"
positiveButton.tag = OnboardingType.welcome.rawValue positiveButton.tag = OnboardingType.welcome.rawValue
backButton.tag = OnboardingType.welcome.rawValue backButton.tag = OnboardingType.welcome.rawValue
[negativeButton, backButton].forEach { $0?.isHidden = true } [negativeButton, backButton].forEach { $0?.isHidden = true }
} }
private func setIdentifiersForTests() { private func setIdentifiersForTests() {
positiveButton.setAccessibilityIdentifier("Forward") positiveButton.setAccessibilityIdentifier("Forward")
negativeButton.setAccessibilityIdentifier("Alternate") negativeButton.setAccessibilityIdentifier("Alternate")
@ -162,21 +162,21 @@ class OnboardingParentViewController: NSViewController {
} }
} else { } else {
self.positiveButton.tag = OnboardingType.complete.rawValue self.positiveButton.tag = OnboardingType.complete.rawValue
// Install the menubar option! // Install the menubar option!
let appDelegate = NSApplication.shared.delegate as? AppDelegate let appDelegate = NSApplication.shared.delegate as? AppDelegate
appDelegate?.continueUsually() appDelegate?.continueUsually()
view.window?.close() view.window?.close()
if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) == false { if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) == false {
UserDefaults.standard.set(true, forKey: CLShowOnboardingFlow) UserDefaults.standard.set(true, forKey: CLShowOnboardingFlow)
} }
} }
} }
private func addChildIfNeccessary(_ vc: NSViewController) { private func addChildIfNeccessary(_ vc: NSViewController) {
if children.contains(vc) == false { if children.contains(vc) == false {
addChild(vc) addChild(vc)
@ -228,12 +228,12 @@ class OnboardingParentViewController: NSViewController {
self.negativeButton.isHidden = false self.negativeButton.isHidden = false
} }
} else if backButton.tag == OnboardingType.search.rawValue { } else if backButton.tag == OnboardingType.search.rawValue {
guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else { guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else {
assertionFailure() assertionFailure()
return return
} }
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
@ -242,17 +242,17 @@ class OnboardingParentViewController: NSViewController {
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
} }
} }
} }
private func shouldStartAtLogin(_ shouldStart: Bool) { private func shouldStartAtLogin(_ shouldStart: Bool) {
// If tests are going on, we don't want to enable/disable launch at login! // If tests are going on, we don't want to enable/disable launch at login!
if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) { if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) {
return return
} }
UserDefaults.standard.set(shouldStart ? 1 : 0, forKey: CLStartAtLogin) UserDefaults.standard.set(shouldStart ? 1 : 0, forKey: CLStartAtLogin)
if !SMLoginItemSetEnabled("com.abhishek.ClockerHelper" as CFString, shouldStart) { if !SMLoginItemSetEnabled("com.abhishek.ClockerHelper" as CFString, shouldStart) {
@ -260,22 +260,22 @@ class OnboardingParentViewController: NSViewController {
} else { } else {
Logger.log(object: ["Successful": "YES"], for: "Start Clocker Login") Logger.log(object: ["Successful": "YES"], for: "Start Clocker Login")
} }
if shouldStart { if shouldStart {
Logger.log(object: [:], for: "Enable Launch at Login while Onboarding") Logger.log(object: [:], for: "Enable Launch at Login while Onboarding")
} else { } else {
Logger.log(object: [:], for: "Disable Launch at Login while Onboarding") Logger.log(object: [:], for: "Disable Launch at Login while Onboarding")
} }
} }
func logExitPoint() { func logExitPoint() {
let currentViewController = currentController() let currentViewController = currentController()
print(currentViewController) print(currentViewController)
Logger.log(object: currentViewController, for: "Onboarding Process Exit") Logger.log(object: currentViewController, for: "Onboarding Process Exit")
} }
private func currentController() -> [String: String] { private func currentController() -> [String: String] {
switch positiveButton.tag { switch positiveButton.tag {
case 0: case 0:
return ["Onboarding Process Interrupted": "Welcome View"] return ["Onboarding Process Interrupted": "Welcome View"]

10
Clocker/Onboarding/OnboardingPermissionsViewController.swift

@ -94,16 +94,16 @@ class OnboardingPermissionsViewController: NSViewController {
guard let `self` = self else { return } guard let `self` = self else { return }
self.calendarActivityIndicator.stopAnimation(nil) self.calendarActivityIndicator.stopAnimation(nil)
if granted { if granted {
self.calendarGrantButton.title = "Granted" self.calendarGrantButton.title = "Granted"
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
// Used to update CalendarViewController's view // Used to update CalendarViewController's view
NotificationCenter.default.post(name: .calendarAccessGranted, object: nil) NotificationCenter.default.post(name: .calendarAccessGranted, object: nil)
} else { } else {
Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted") Logger.log(object: ["Reminder Access Not Granted": "YES"], for: "Reminder Access Not Granted")
} }
@ -130,10 +130,10 @@ class OnboardingPermissionsViewController: NSViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
self.reminderGrantButton.title = "Granted" self.reminderGrantButton.title = "Granted"
} }
} else { } else {

88
Clocker/Onboarding/OnboardingSearchController.swift

@ -15,7 +15,7 @@ class OnboardingSearchController: NSViewController {
@IBOutlet private var resultsTableView: NSTableView! @IBOutlet private var resultsTableView: NSTableView!
@IBOutlet private var accessoryLabel: NSTextField! @IBOutlet private var accessoryLabel: NSTextField!
@IBOutlet weak var undoButton: NSButton! @IBOutlet weak var undoButton: NSButton!
private var results: [TimezoneData] = [] private var results: [TimezoneData] = []
private var dataTask: URLSessionDataTask? = .none private var dataTask: URLSessionDataTask? = .none
@ -36,29 +36,29 @@ class OnboardingSearchController: NSViewController {
} }
resultsTableView.reloadData() resultsTableView.reloadData()
func setupUndoButton() { func setupUndoButton() {
let font = NSFont(name: "Avenir", size: 13)! let font = NSFont(name: "Avenir", size: 13)!
let attributes = [NSAttributedString.Key.foregroundColor: NSColor.linkColor, let attributes = [NSAttributedString.Key.foregroundColor: NSColor.linkColor,
NSAttributedString.Key.font: font] NSAttributedString.Key.font: font]
undoButton.attributedTitle = NSAttributedString(string: "UNDO", attributes: attributes) undoButton.attributedTitle = NSAttributedString(string: "UNDO", attributes: attributes)
} }
setupUndoButton() setupUndoButton()
} }
@objc func doubleClickAction(_: NSTableView?) { @objc func doubleClickAction(_: NSTableView?) {
[accessoryLabel].forEach { $0?.isHidden = false } [accessoryLabel].forEach { $0?.isHidden = false }
if resultsTableView.selectedRow >= 0 && resultsTableView.selectedRow < results.count { if resultsTableView.selectedRow >= 0 && resultsTableView.selectedRow < results.count {
let selectedTimezone = results[resultsTableView.selectedRow] let selectedTimezone = results[resultsTableView.selectedRow]
addTimezoneToDefaults(selectedTimezone) addTimezoneToDefaults(selectedTimezone)
} }
} }
private func addTimezoneToDefaults(_ timezone: TimezoneData) { private func addTimezoneToDefaults(_ timezone: TimezoneData) {
func setupLabelHidingTimer() { func setupLabelHidingTimer() {
Timer.scheduledTimer(withTimeInterval: 5, Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in repeats: false) { _ in
@ -67,8 +67,7 @@ class OnboardingSearchController: NSViewController {
} }
} }
} }
if resultsTableView.selectedRow == -1 { if resultsTableView.selectedRow == -1 {
setInfoLabel(PreferencesConstants.noTimezoneSelectedErrorMessage) setInfoLabel(PreferencesConstants.noTimezoneSelectedErrorMessage)
setupLabelHidingTimer() setupLabelHidingTimer()
@ -88,7 +87,7 @@ class OnboardingSearchController: NSViewController {
fetchTimezone(for: latitude, and: longitude, timezone) fetchTimezone(for: latitude, and: longitude, timezone)
} }
// We want to display the undo button only if we've added a timezone. // We want to display the undo button only if we've added a timezone.
// If else, we want it hidden. This below method ensures that. // If else, we want it hidden. This below method ensures that.
private func setInfoLabel(_ text: String) { private func setInfoLabel(_ text: String) {
@ -98,12 +97,12 @@ class OnboardingSearchController: NSViewController {
/// Returns true if there's an error. /// Returns true if there's an error.
private func handleEdgeCase(for response: Data?) -> Bool { private func handleEdgeCase(for response: Data?) -> Bool {
func setErrorPlaceholders() { func setErrorPlaceholders() {
setInfoLabel("No timezone found! Try entering an exact name.") setInfoLabel("No timezone found! Try entering an exact name.")
searchBar.placeholderString = placeholders.randomElement() searchBar.placeholderString = placeholders.randomElement()
} }
guard let json = response, let jsonUnserialized = try? JSONSerialization.jsonObject(with: json, options: .allowFragments), let unwrapped = jsonUnserialized as? [String: Any] else { guard let json = response, let jsonUnserialized = try? JSONSerialization.jsonObject(with: json, options: .allowFragments), let unwrapped = jsonUnserialized as? [String: Any] else {
setErrorPlaceholders() setErrorPlaceholders()
return true return true
@ -143,7 +142,7 @@ class OnboardingSearchController: NSViewController {
let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)&timestamp=\(timeStamp)&key=\(CLGeocodingKey)" let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)&timestamp=\(timeStamp)&key=\(CLGeocodingKey)"
NetworkManager.task(with: urlString) { [weak self] response, error in NetworkManager.task(with: urlString) { [weak self] response, error in
guard let `self` = self else { return } guard let `self` = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
@ -166,7 +165,7 @@ class OnboardingSearchController: NSViewController {
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
"nextUpdate": CLEmptyString, "nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress, CLCustomLabel: filteredAddress
] as [String: Any] ] as [String: Any]
DataStore.shared().addTimezone(TimezoneData(with: newTimeZone)) DataStore.shared().addTimezone(TimezoneData(with: newTimeZone))
@ -175,10 +174,10 @@ class OnboardingSearchController: NSViewController {
self.accessoryLabel.stringValue = "Added \(filteredAddress)." self.accessoryLabel.stringValue = "Added \(filteredAddress)."
self.undoButton.isHidden = false self.undoButton.isHidden = false
Logger.log(object: ["Place Name": filteredAddress], for: "Added Timezone while Onboarding") Logger.log(object: ["Place Name": filteredAddress], for: "Added Timezone while Onboarding")
} }
// Cleanup. // Cleanup.
self.resetSearchView() self.resetSearchView()
} else { } else {
@ -210,7 +209,7 @@ class OnboardingSearchController: NSViewController {
} }
@IBAction func search(_ sender: NSSearchField) { @IBAction func search(_ sender: NSSearchField) {
resultsTableView.deselectAll(nil) resultsTableView.deselectAll(nil)
let searchString = sender.stringValue let searchString = sender.stringValue
@ -235,87 +234,87 @@ class OnboardingSearchController: NSViewController {
NSObject.cancelPreviousPerformRequests(withTarget: self) NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(OnboardingSearchController.actualSearch), with: nil, afterDelay: 0.5) perform(#selector(OnboardingSearchController.actualSearch), with: nil, afterDelay: 0.5)
} }
@objc func actualSearch() { @objc func actualSearch() {
func setupForError() { func setupForError() {
self.resultsTableView.isHidden = true self.resultsTableView.isHidden = true
} }
let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US"
var searchString = searchBar.stringValue var searchString = searchBar.stringValue
let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines) let words = searchString.components(separatedBy: CharacterSet.whitespacesAndNewlines)
searchString = words.joined(separator: CLEmptyString) searchString = words.joined(separator: CLEmptyString)
let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(CLGeocodingKey)&language=\(userPreferredLanguage)" let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchString)&key=\(CLGeocodingKey)&language=\(userPreferredLanguage)"
dataTask = NetworkManager.task(with: urlString, 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 {
print("Search string was: \(searchString)") print("Search string was: \(searchString)")
let currentSearchBarValue = self.searchBar.stringValue let currentSearchBarValue = self.searchBar.stringValue
let words = currentSearchBarValue.components(separatedBy: CharacterSet.whitespacesAndNewlines) let words = currentSearchBarValue.components(separatedBy: CharacterSet.whitespacesAndNewlines)
if words.joined(separator: CLEmptyString) != searchString { if words.joined(separator: CLEmptyString) != searchString {
return return
} }
self.results = [] self.results = []
if let errorPresent = error { if let errorPresent = error {
if errorPresent.localizedDescription == PreferencesConstants.offlineErrorMessage { if errorPresent.localizedDescription == PreferencesConstants.offlineErrorMessage {
self.setInfoLabel(PreferencesConstants.noInternetConnectivityError) self.setInfoLabel(PreferencesConstants.noInternetConnectivityError)
} else { } else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage) self.setInfoLabel(PreferencesConstants.tryAgainMessage)
} }
setupForError() setupForError()
return return
} }
guard let data = response else { guard let data = response else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage) self.setInfoLabel(PreferencesConstants.tryAgainMessage)
setupForError() setupForError()
return return
} }
let searchResults = self.decode(from: data) let searchResults = self.decode(from: data)
if searchResults?.status == "ZERO_RESULTS" { if searchResults?.status == "ZERO_RESULTS" {
self.setInfoLabel("No results! 😔 Try entering the exact name.") self.setInfoLabel("No results! 😔 Try entering the exact name.")
setupForError() setupForError()
return return
} }
for result in searchResults!.results { for result in searchResults!.results {
let location = result.geometry.location let location = result.geometry.location
let latitude = location.lat let latitude = location.lat
let longitude = location.lng let longitude = location.lng
let formattedAddress = result.formattedAddress let formattedAddress = result.formattedAddress
let totalPackage = [ let totalPackage = [
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId, CLPlaceIdentifier: result.placeId
] as [String: Any] ] as [String: Any]
self.results.append(TimezoneData(with: totalPackage)) self.results.append(TimezoneData(with: totalPackage))
} }
self.setInfoLabel(CLEmptyString) self.setInfoLabel(CLEmptyString)
self.resultsTableView.reloadData() self.resultsTableView.reloadData()
} }
}) })
@ -339,13 +338,12 @@ class OnboardingSearchController: NSViewController {
searchBar.stringValue = CLEmptyString searchBar.stringValue = CLEmptyString
searchBar.placeholderString = placeholders.randomElement() searchBar.placeholderString = placeholders.randomElement()
} }
@IBAction func undoAction(_ sender: Any) { @IBAction func undoAction(_ sender: Any) {
DataStore.shared().removeLastTimezone() DataStore.shared().removeLastTimezone()
setInfoLabel("Removed.") setInfoLabel("Removed.")
} }
} }
extension OnboardingSearchController: NSTableViewDataSource { extension OnboardingSearchController: NSTableViewDataSource {

64
Clocker/Overall App/AppDefaults.swift

@ -3,82 +3,82 @@
import Cocoa import Cocoa
class AppDefaults { class AppDefaults {
class func initialize() { class func initialize() {
initializeDefaults() initializeDefaults()
} }
private class func deleteOldUserDefaults() { private class func deleteOldUserDefaults() {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
// Now delete the old preferences // Now delete the old preferences
if let bundleID = Bundle.main.bundleIdentifier, userDefaults.object(forKey: "PreferencesHaveBeenWiped") == nil { if let bundleID = Bundle.main.bundleIdentifier, userDefaults.object(forKey: "PreferencesHaveBeenWiped") == nil {
userDefaults.removePersistentDomain(forName: bundleID) userDefaults.removePersistentDomain(forName: bundleID)
userDefaults.set(true, forKey: "PreferencesHaveBeenWiped") userDefaults.set(true, forKey: "PreferencesHaveBeenWiped")
} }
} }
private class func initializeDefaults() { private class func initializeDefaults() {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
let menubarFavourites = userDefaults.object(forKey: CLMenubarFavorites) let menubarFavourites = userDefaults.object(forKey: CLMenubarFavorites)
let timezones = userDefaults.object(forKey: CLDefaultPreferenceKey) let timezones = userDefaults.object(forKey: CLDefaultPreferenceKey)
let selectedCalendars = userDefaults.object(forKey: CLSelectedCalendars) let selectedCalendars = userDefaults.object(forKey: CLSelectedCalendars)
// Now delete the old preferences // Now delete the old preferences
userDefaults.wipeIfNeccesary() userDefaults.wipeIfNeccesary()
// Register the usual suspects // Register the usual suspects
userDefaults.register(defaults: defaultsDictionary()) userDefaults.register(defaults: defaultsDictionary())
// Set arrays and our custom objects // Set arrays and our custom objects
userDefaults.set(menubarFavourites, forKey: CLMenubarFavorites) userDefaults.set(menubarFavourites, forKey: CLMenubarFavorites)
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey) userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
userDefaults.set(selectedCalendars, forKey: CLSelectedCalendars) userDefaults.set(selectedCalendars, forKey: CLSelectedCalendars)
// Set the theme default as Light! // Set the theme default as Light!
setDefaultTheme() setDefaultTheme()
// If we already have timezones to display in menubar, do nothing. // If we already have timezones to display in menubar, do nothing.
// Else, we switch the menubar mode default to compact mode for new users // Else, we switch the menubar mode default to compact mode for new users
if userDefaults.bool(forKey: CLDefaultMenubarMode) == false { if userDefaults.bool(forKey: CLDefaultMenubarMode) == false {
if let menubarFavourites = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data], menubarFavourites.isEmpty == false { if let menubarFavourites = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data], menubarFavourites.isEmpty == false {
userDefaults.set(1, forKey: CLMenubarCompactMode) userDefaults.set(1, forKey: CLMenubarCompactMode)
} else { } else {
userDefaults.set(0, forKey: CLMenubarCompactMode) userDefaults.set(0, forKey: CLMenubarCompactMode)
} }
userDefaults.set(true, forKey: CLDefaultMenubarMode) userDefaults.set(true, forKey: CLDefaultMenubarMode)
} }
if userDefaults.bool(forKey: CLSwitchToCompactModeAlert) == false { if userDefaults.bool(forKey: CLSwitchToCompactModeAlert) == false {
userDefaults.set(true, forKey: CLSwitchToCompactModeAlert) userDefaults.set(true, forKey: CLSwitchToCompactModeAlert)
if let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count > 1 { if let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count > 1 {
// If the user is already using the compact mode, abort. // If the user is already using the compact mode, abort.
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if DataStore.shared().shouldDisplay(.menubarCompactMode) {
return return
} }
showCompactModeAlert() showCompactModeAlert()
} }
} }
} }
private class func setDefaultTheme() { private class func setDefaultTheme() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
if defaults.object(forKey: CLThemeKey) == nil { if defaults.object(forKey: CLThemeKey) == nil {
Themer.shared().set(theme: 0) Themer.shared().set(theme: 0)
} }
// Set default to System theme for Mojave // Set default to System theme for Mojave
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
if defaults.bool(forKey: CLDefaultThemeOnMojave) == false { if defaults.bool(forKey: CLDefaultThemeOnMojave) == false {
if isDarkModeOn() { if isDarkModeOn() {
Themer.shared().set(theme: 2) Themer.shared().set(theme: 2)
@ -88,34 +88,34 @@ class AppDefaults {
} }
} }
} }
private class func showCompactModeAlert() { private class func showCompactModeAlert() {
// Time to display the alert. // Time to display the alert.
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert() let alert = NSAlert()
alert.messageText = "Save space on your menu bar" alert.messageText = "Save space on your menu bar"
alert.informativeText = "Enable Menubar Compact Mode to fit in more timezones in less space!" alert.informativeText = "Enable Menubar Compact Mode to fit in more timezones in less space!"
alert.addButton(withTitle: "Enable Compact Mode") alert.addButton(withTitle: "Enable Compact Mode")
alert.addButton(withTitle: "Cancel") alert.addButton(withTitle: "Cancel")
let response = alert.runModal() let response = alert.runModal()
if response.rawValue == 1000 { if response.rawValue == 1000 {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
UserDefaults.standard.set(0, forKey: CLMenubarCompactMode) UserDefaults.standard.set(0, forKey: CLMenubarCompactMode)
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return return
} }
statusItem.setupStatusItem() statusItem.setupStatusItem()
Logger.log(object: ["Context": "On Launch"], for: "Switched to Compact Mode") Logger.log(object: ["Context": "On Launch"], for: "Switched to Compact Mode")
} }
} }
} }
private class func defaultsDictionary() -> [String: Any] { private class func defaultsDictionary() -> [String: Any] {
return [CLThemeKey: 0, return [CLThemeKey: 0,
CLDisplayFutureSliderKey: 0, CLDisplayFutureSliderKey: 0,
@ -143,12 +143,12 @@ class AppDefaults {
} }
extension UserDefaults { extension UserDefaults {
func wipeIfNeccesary() { func wipeIfNeccesary() {
if let bundleID = Bundle.main.bundleIdentifier, object(forKey: "PreferencesHaveBeenWiped") == nil { if let bundleID = Bundle.main.bundleIdentifier, object(forKey: "PreferencesHaveBeenWiped") == nil {
removePersistentDomain(forName: bundleID) removePersistentDomain(forName: bundleID)
set(true, forKey: "PreferencesHaveBeenWiped") set(true, forKey: "PreferencesHaveBeenWiped")
} }
} }
} }

18
Clocker/Overall App/AppKit + Additions.swift

@ -1,42 +1,42 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
extension NSTextField { extension NSTextField {
func applyDefaultStyle() { func applyDefaultStyle() {
backgroundColor = NSColor.clear backgroundColor = NSColor.clear
isEditable = false isEditable = false
isBordered = false isBordered = false
allowsDefaultTighteningForTruncation = true allowsDefaultTighteningForTruncation = true
if #available(OSX 10.12.2, *) { if #available(OSX 10.12.2, *) {
isAutomaticTextCompletionEnabled = false isAutomaticTextCompletionEnabled = false
allowsCharacterPickerTouchBarItem = false allowsCharacterPickerTouchBarItem = false
} }
} }
func disableWrapping() { func disableWrapping() {
usesSingleLineMode = false usesSingleLineMode = false
cell?.wraps = false cell?.wraps = false
cell?.isScrollable = true cell?.isScrollable = true
} }
} }
extension NSFont { extension NSFont {
func size(_ string: String, _ width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize { func size(_ string: String, _ width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize {
let size = CGSize(width: width, let size = CGSize(width: width,
height: Double.greatestFiniteMagnitude) height: Double.greatestFiniteMagnitude)
var otherAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font: self] var otherAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font: self]
attributes.forEach { (arg) in let (key, value) = arg; otherAttributes[key] = value } attributes.forEach { (arg) in let (key, value) = arg; otherAttributes[key] = value }
return NSString(string: string).boundingRect(with: size, return NSString(string: string).boundingRect(with: size,
options: NSString.DrawingOptions.usesLineFragmentOrigin, options: NSString.DrawingOptions.usesLineFragmentOrigin,
attributes: attributes).size attributes: attributes).size
} }
} }
class ClockerSearchField: NSSearchField { class ClockerSearchField: NSSearchField {

22
Clocker/Overall App/DataStore.swift

@ -20,7 +20,7 @@ enum ViewType {
class DataStore: NSObject { class DataStore: NSObject {
private static var sharedStore = DataStore(with: UserDefaults.standard) private static var sharedStore = DataStore(with: UserDefaults.standard)
private var userDefaults: UserDefaults! private var userDefaults: UserDefaults!
// Since this pref can accessed every second, let's cache this // Since this pref can accessed every second, let's cache this
private var shouldDisplayDateInMenubar: Bool = false private var shouldDisplayDateInMenubar: Bool = false
@ -41,7 +41,7 @@ class DataStore: NSObject {
return preferences return preferences
} }
func updateDayPreference() { func updateDayPreference() {
shouldDisplayDateInMenubar = shouldDisplay(.dayInMenubar) shouldDisplayDateInMenubar = shouldDisplay(.dayInMenubar)
} }
@ -49,7 +49,7 @@ class DataStore: NSObject {
func shouldShowDateInMenubar() -> Bool { func shouldShowDateInMenubar() -> Bool {
return shouldDisplayDateInMenubar return shouldDisplayDateInMenubar
} }
func setTimezones(_ timezones: [Data]) { func setTimezones(_ timezones: [Data]) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey) userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
} }
@ -66,18 +66,18 @@ class DataStore: NSObject {
userDefaults.set(defaults, forKey: CLDefaultPreferenceKey) userDefaults.set(defaults, forKey: CLDefaultPreferenceKey)
} }
func removeLastTimezone() { func removeLastTimezone() {
var currentLineup = timezones() var currentLineup = timezones()
if currentLineup.isEmpty { if currentLineup.isEmpty {
return return
} }
currentLineup.removeLast() currentLineup.removeLast()
Logger.log(object: [:], for: "Undo Action Executed during Onboarding") Logger.log(object: [:], for: "Undo Action Executed during Onboarding")
userDefaults.set(currentLineup, forKey: CLDefaultPreferenceKey) userDefaults.set(currentLineup, forKey: CLDefaultPreferenceKey)
} }
@ -157,13 +157,13 @@ class DataStore: NSObject {
return false return false
} }
return value.isEqual(to: NSNumber(value: 0)) return value.isEqual(to: NSNumber(value: 0))
case .menubarCompactMode: case .menubarCompactMode:
guard let value = retrieve(key: CLMenubarCompactMode) as? Int else { guard let value = retrieve(key: CLMenubarCompactMode) as? Int else {
return false return false
} }
return value == 0 return value == 0
} }
} }

6
Clocker/Overall App/DateFormatterManager.swift

@ -45,7 +45,7 @@ class DateFormatterManager: NSObject {
specializedFormatter.locale = locale specializedFormatter.locale = locale
return specializedFormatter return specializedFormatter
} }
@objc class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale: Locale = Locale.autoupdatingCurrent) -> DateFormatter { @objc class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale: Locale = Locale.autoupdatingCurrent) -> DateFormatter {
dateFormatter.dateStyle = .none dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none dateFormatter.timeStyle = .none
@ -54,7 +54,7 @@ class DateFormatterManager: NSObject {
dateFormatter.timeZone = TimeZone(identifier: timezoneIdentifier) dateFormatter.timeZone = TimeZone(identifier: timezoneIdentifier)
return dateFormatter return dateFormatter
} }
@objc class func localizedCalendaricalDateFormatter(with format: String) -> DateFormatter { @objc class func localizedCalendaricalDateFormatter(with format: String) -> DateFormatter {
calendarDateFormatter.dateStyle = .none calendarDateFormatter.dateStyle = .none
calendarDateFormatter.timeStyle = .none calendarDateFormatter.timeStyle = .none
@ -63,7 +63,7 @@ class DateFormatterManager: NSObject {
calendarDateFormatter.calendar = gregorianCalendar calendarDateFormatter.calendar = gregorianCalendar
return calendarDateFormatter return calendarDateFormatter
} }
@objc class func localizedSimpleFormatter(_ format: String) -> DateFormatter { @objc class func localizedSimpleFormatter(_ format: String) -> DateFormatter {
localizedSimpleFormatter.dateStyle = .none localizedSimpleFormatter.dateStyle = .none
localizedSimpleFormatter.timeStyle = .none localizedSimpleFormatter.timeStyle = .none

2
Clocker/Overall App/NetworkManager.swift

@ -15,7 +15,7 @@ class NetworkManager: NSObject {
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 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: "There was a problem searching the location. Please try again later. If the problem continues please contact App Support."]

34
Clocker/Overall App/Themer.swift

@ -6,7 +6,7 @@ func isDarkModeOn() -> Bool {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
return NSAppearance.current.name == NSAppearance.Name.darkAqua return NSAppearance.current.name == NSAppearance.Name.darkAqua
} }
return false return false
} }
@ -42,11 +42,11 @@ class Themer: NSObject {
default: default:
themeIndex = Theme.light themeIndex = Theme.light
} }
super.init() super.init()
setAppAppearance() setAppAppearance()
DistributedNotificationCenter.default.addObserver(self, DistributedNotificationCenter.default.addObserver(self,
selector: #selector(respondToInterfaceStyle), selector: #selector(respondToInterfaceStyle),
name: .interfaceStyleDidChange, name: .interfaceStyleDidChange,
@ -74,17 +74,17 @@ extension Themer {
default: default:
themeIndex = Theme.light themeIndex = Theme.light
} }
setAppAppearance() setAppAppearance()
} }
@objc func respondToInterfaceStyle() { @objc func respondToInterfaceStyle() {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.setAppAppearance() self.setAppAppearance()
} }
} }
private func setAppAppearance() { private func setAppAppearance() {
if #available(OSX 10.14, *) { if #available(OSX 10.14, *) {
var appAppearance = NSAppearance(named: .aqua) var appAppearance = NSAppearance(named: .aqua)
@ -270,7 +270,7 @@ extension Themer {
return themeIndex == .light ? NSImage(named: NSImage.Name("Extra"))! : NSImage(named: NSImage.Name("ExtraWhite"))! return themeIndex == .light ? NSImage(named: NSImage.Name("Extra"))! : NSImage(named: NSImage.Name("ExtraWhite"))!
} }
func menubarOnboardingImage() -> NSImage { func menubarOnboardingImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
@ -280,7 +280,7 @@ extension Themer {
return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSImage(named: NSImage.Name("Dark Menubar"))! : NSImage(named: NSImage.Name("Light Menubar"))! return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSImage(named: NSImage.Name("Dark Menubar"))! : NSImage(named: NSImage.Name("Light Menubar"))!
} }
} }
return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSImage(named: NSImage.Name("Dark Menubar"))! : NSImage(named: NSImage.Name("Light Menubar"))! return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSImage(named: NSImage.Name("Dark Menubar"))! : NSImage(named: NSImage.Name("Light Menubar"))!
} }
@ -315,7 +315,7 @@ extension Themer {
} }
func currentLocationImage() -> NSImage { func currentLocationImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -326,7 +326,7 @@ extension Themer {
return NSImage(named: NSImage.Name("CurrentLocationDynamic"))! return NSImage(named: NSImage.Name("CurrentLocationDynamic"))!
} }
} }
return themeIndex == .light ? NSImage(named: NSImage.Name("CurrentLocation"))! : NSImage(named: NSImage.Name("CurrentLocationWhite"))! return themeIndex == .light ? NSImage(named: NSImage.Name("CurrentLocation"))! : NSImage(named: NSImage.Name("CurrentLocationWhite"))!
} }
@ -365,7 +365,7 @@ extension Themer {
} }
func privacyTabImage() -> NSImage { func privacyTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -376,12 +376,12 @@ extension Themer {
return NSImage(named: .permissionTabIcon)! return NSImage(named: .permissionTabIcon)!
} }
} }
return themeIndex == .light ? NSImage(named: NSImage.Name("Privacy"))! : NSImage(named: NSImage.Name("Privacy Dark"))! return themeIndex == .light ? NSImage(named: NSImage.Name("Privacy"))! : NSImage(named: NSImage.Name("Privacy Dark"))!
} }
func appearanceTabImage() -> NSImage { func appearanceTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -392,12 +392,12 @@ extension Themer {
return NSImage(named: .appearanceTabIcon)! return NSImage(named: .appearanceTabIcon)!
} }
} }
return themeIndex == .light ? NSImage(named: NSImage.Name("Appearance"))! : NSImage(named: NSImage.Name("Appearance Dark"))! return themeIndex == .light ? NSImage(named: NSImage.Name("Appearance"))! : NSImage(named: NSImage.Name("Appearance Dark"))!
} }
func calendarTabImage() -> NSImage { func calendarTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -408,7 +408,7 @@ extension Themer {
return NSImage(named: .calendarTabIcon)! return NSImage(named: .calendarTabIcon)!
} }
} }
return themeIndex == .light ? NSImage(named: NSImage.Name("Calendar Tab Icon"))! : NSImage(named: NSImage.Name("Calendar Tab Dark"))! return themeIndex == .light ? NSImage(named: NSImage.Name("Calendar Tab Icon"))! : NSImage(named: NSImage.Name("Calendar Tab Dark"))!
} }

26
Clocker/Panel/Data Layer/TimezoneData.swift

@ -12,7 +12,7 @@ struct DateFormat {
// Non-class type cannot conform to NSCoding! // Non-class type cannot conform to NSCoding!
class TimezoneData: NSObject, NSCoding { class TimezoneData: NSObject, NSCoding {
enum SelectionType: Int { enum SelectionType: Int {
case city case city
case timezone case timezone
@ -28,7 +28,7 @@ class TimezoneData: NSObject, NSCoding {
case twentyFourFormat case twentyFourFormat
case globalFormat case globalFormat
} }
enum SecondsOverride: Int { enum SecondsOverride: Int {
case yes case yes
case no case no
@ -170,7 +170,7 @@ class TimezoneData: NSObject, NSCoding {
let override = aDecoder.decodeInteger(forKey: "overrideFormat") let override = aDecoder.decodeInteger(forKey: "overrideFormat")
overrideFormat = TimezoneOverride(rawValue: override)! overrideFormat = TimezoneOverride(rawValue: override)!
let secondsOverride = aDecoder.decodeInteger(forKey: "secondsOverrideFormat") let secondsOverride = aDecoder.decodeInteger(forKey: "secondsOverrideFormat")
overrideSecondsFormat = SecondsOverride(rawValue: secondsOverride)! overrideSecondsFormat = SecondsOverride(rawValue: secondsOverride)!
} }
@ -189,20 +189,20 @@ class TimezoneData: NSObject, NSCoding {
return nil return nil
} }
private class func logOldModelUsage() { private class func logOldModelUsage() {
guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String, guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else {
return return
} }
let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion
let osVersion = "\(operatingSystem.majorVersion).\(operatingSystem.minorVersion).\(operatingSystem.patchVersion)" let osVersion = "\(operatingSystem.majorVersion).\(operatingSystem.minorVersion).\(operatingSystem.patchVersion)"
let versionInfo = "Clocker \(shortVersion) (\(appVersion))" let versionInfo = "Clocker \(shortVersion) (\(appVersion))"
let feedbackInfo = [ let feedbackInfo = [
AppFeedbackConstants.CLOperatingSystemVersion: osVersion, AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo, AppFeedbackConstants.CLClockerVersion: versionInfo
] ]
Logger.log(object: feedbackInfo, for: "CLTimezoneData is still being used!") Logger.log(object: feedbackInfo, for: "CLTimezoneData is still being used!")
@ -290,7 +290,7 @@ class TimezoneData: NSObject, NSCoding {
aCoder.encode(isSystemTimezone, forKey: "isSystemTimezone") aCoder.encode(isSystemTimezone, forKey: "isSystemTimezone")
aCoder.encode(overrideFormat.rawValue, forKey: "overrideFormat") aCoder.encode(overrideFormat.rawValue, forKey: "overrideFormat")
aCoder.encode(overrideSecondsFormat.rawValue, forKey: "secondsOverrideFormat") aCoder.encode(overrideSecondsFormat.rawValue, forKey: "secondsOverrideFormat")
} }
@ -335,7 +335,7 @@ class TimezoneData: NSObject, NSCoding {
overrideFormat = .globalFormat overrideFormat = .globalFormat
} }
} }
func setShouldOverrideSecondsFormat(_ shouldOverride: Int) { func setShouldOverrideSecondsFormat(_ shouldOverride: Int) {
if shouldOverride == 0 { if shouldOverride == 0 {
overrideSecondsFormat = .yes overrideSecondsFormat = .yes
@ -361,7 +361,7 @@ class TimezoneData: NSObject, NSCoding {
let errorDictionary = [ let errorDictionary = [
"Formatted Address": name, "Formatted Address": name,
"Place Identifier": placeIdentifier, "Place Identifier": placeIdentifier,
"TimezoneID": timezoneIdentifier, "TimezoneID": timezoneIdentifier
] ]
Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData") Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData")
@ -396,15 +396,15 @@ class TimezoneData: NSObject, NSCoding {
return timeFormat return timeFormat
} }
func shouldDisplayTwelveHourFormat() -> Bool { func shouldDisplayTwelveHourFormat() -> Bool {
if overrideSecondsFormat == .globalFormat { if overrideSecondsFormat == .globalFormat {
return DataStore.shared().shouldDisplay(.twelveHour) return DataStore.shared().shouldDisplay(.twelveHour)
} }
return overrideFormat == .twelveHourFormat return overrideFormat == .twelveHourFormat
} }
func shouldShowSeconds() -> Bool { func shouldShowSeconds() -> Bool {
if overrideSecondsFormat == .globalFormat { if overrideSecondsFormat == .globalFormat {
return DataStore.shared().shouldDisplay(.seconds) return DataStore.shared().shouldDisplay(.seconds)

52
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -31,20 +31,20 @@ extension TimezoneDataOperations {
return dateFormatter.string(from: newDate) return dateFormatter.string(from: newDate)
} }
func compactMenuHeader() -> String { func compactMenuHeader() -> String {
var subtitle = CLEmptyString var subtitle = CLEmptyString
let shouldDayBeShown = DataStore.shared().shouldShowDateInMenubar() let shouldDayBeShown = DataStore.shared().shouldShowDateInMenubar()
if shouldDayBeShown { if shouldDayBeShown {
let substring = date(with: 0, displayType: CLDateDisplayType.menuDisplay) let substring = date(with: 0, displayType: CLDateDisplayType.menuDisplay)
subtitle.append(substring) subtitle.append(substring)
} }
subtitle.isEmpty ? subtitle.append(time(with: 0)) : subtitle.append(" \(time(with: 0))") subtitle.isEmpty ? subtitle.append(time(with: 0)) : subtitle.append(" \(time(with: 0))")
return subtitle return subtitle
} }
@ -99,26 +99,26 @@ extension TimezoneDataOperations {
return menuTitle return menuTitle
} }
private func timezoneDate(with sliderValue: Int, _ calendar: Calendar) -> Date { private func timezoneDate(with sliderValue: Int, _ calendar: Calendar) -> Date {
let source = timezoneDateByAdding(minutesToAdd: sliderValue, calendar) let source = timezoneDateByAdding(minutesToAdd: sliderValue, calendar)
let sourceTimezone = TimeZone.current let sourceTimezone = TimeZone.current
let destinationTimezone = TimeZone(identifier: dataObject.timezone()) let destinationTimezone = TimeZone(identifier: dataObject.timezone())
let sourceGMTOffset: Double = Double(sourceTimezone.secondsFromGMT(for: source)) let sourceGMTOffset: Double = Double(sourceTimezone.secondsFromGMT(for: source))
let destinationGMTOffset: Double = Double(destinationTimezone?.secondsFromGMT(for: source) ?? 0) let destinationGMTOffset: Double = Double(destinationTimezone?.secondsFromGMT(for: source) ?? 0)
let interval = destinationGMTOffset - sourceGMTOffset let interval = destinationGMTOffset - sourceGMTOffset
return Date(timeInterval: interval, since: source) return Date(timeInterval: interval, since: source)
} }
// calendar.dateByAdding takes a 0.1% or 0.2% according to TimeProfiler // calendar.dateByAdding takes a 0.1% or 0.2% according to TimeProfiler
// Let's not use it unless neccesary! // Let's not use it unless neccesary!
private func timezoneDateByAdding(minutesToAdd: Int, _ calendar: Calendar?) -> Date { private func timezoneDateByAdding(minutesToAdd: Int, _ calendar: Calendar?) -> Date {
if minutesToAdd == 0 { if minutesToAdd == 0 {
return Date() return Date()
} }
return calendar?.date(byAdding: .minute, return calendar?.date(byAdding: .minute,
value: minutesToAdd, value: minutesToAdd,
to: Date()) ?? Date() to: Date()) ?? Date()
@ -127,7 +127,7 @@ extension TimezoneDataOperations {
func date(with sliderValue: Int, displayType: CLDateDisplayType) -> String { func date(with sliderValue: Int, displayType: CLDateDisplayType) -> String {
var currentCalendar = Calendar(identifier: .gregorian) var currentCalendar = Calendar(identifier: .gregorian)
currentCalendar.locale = Locale.autoupdatingCurrent currentCalendar.locale = Locale.autoupdatingCurrent
let convertedDate = timezoneDate(with: sliderValue, currentCalendar) let convertedDate = timezoneDate(with: sliderValue, currentCalendar)
guard let relativeDayPreference = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber else { guard let relativeDayPreference = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber else {
@ -136,17 +136,17 @@ extension TimezoneDataOperations {
} }
if displayType == CLDateDisplayType.panelDisplay { if displayType == CLDateDisplayType.panelDisplay {
// Yesterday, tomorrow, etc // Yesterday, tomorrow, etc
if relativeDayPreference.intValue == 0 { if relativeDayPreference.intValue == 0 {
let localFormatter = DateFormatterManager.localizedSimpleFormatter("EEEE") let localFormatter = DateFormatterManager.localizedSimpleFormatter("EEEE")
let local = localFormatter.date(from: localeDate(with: "EEEE")) let local = localFormatter.date(from: localeDate(with: "EEEE"))
// Gets local week day number and timezone's week day number for comparison // Gets local week day number and timezone's week day number for comparison
let weekDay = currentCalendar.component(.weekday, from: local!) let weekDay = currentCalendar.component(.weekday, from: local!)
let timezoneWeekday = currentCalendar.component(.weekday, from: convertedDate) let timezoneWeekday = currentCalendar.component(.weekday, from: convertedDate)
if weekDay == timezoneWeekday + 1 { if weekDay == timezoneWeekday + 1 {
return "Yesterday\(timeDifference())" return "Yesterday\(timeDifference())"
} else if weekDay == timezoneWeekday { } else if weekDay == timezoneWeekday {
@ -157,7 +157,7 @@ extension TimezoneDataOperations {
return "\(weekdayText(from: convertedDate))\(timeDifference())" return "\(weekdayText(from: convertedDate))\(timeDifference())"
} }
} }
// Day name: Thursday, Friday etc // Day name: Thursday, Friday etc
if relativeDayPreference.intValue == 1 { if relativeDayPreference.intValue == 1 {
return "\(weekdayText(from: convertedDate))\(timeDifference())" return "\(weekdayText(from: convertedDate))\(timeDifference())"
@ -167,27 +167,27 @@ extension TimezoneDataOperations {
if relativeDayPreference.intValue == 2 { if relativeDayPreference.intValue == 2 {
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()]
Logger.log(object: errorDictionary, for: "Unable to get date") Logger.log(object: errorDictionary, for: "Unable to get date")
return "Error" return "Error"
} else { } else {
return "\(shortWeekdayText(convertedDate))" return "\(shortWeekdayText(convertedDate))"
} }
} }
// Returns shortened weekday given a date // Returns shortened weekday given a date
// For eg. Thu or Thursday, Tues for Tuesday etc // For eg. Thu or Thursday, Tues for Tuesday etc
private func shortWeekdayText(_ date: Date) -> String { private func shortWeekdayText(_ date: Date) -> String {
let localizedFormatter = DateFormatterManager.localizedSimpleFormatter("E") let localizedFormatter = DateFormatterManager.localizedSimpleFormatter("E")
return localizedFormatter.string(from: date) return localizedFormatter.string(from: date)
} }
// Returns proper weekday given a date // Returns proper weekday given a date
// For eg. Thursday, Sunday, Friday etc // For eg. Thursday, Sunday, Friday etc
private func weekdayText(from date: Date) -> String { private func weekdayText(from date: Date) -> String {
@ -207,12 +207,12 @@ extension TimezoneDataOperations {
let unableToConvertDateParameters = [ let unableToConvertDateParameters = [
"New Date": newDate, "New Date": newDate,
"Timezone": dataObject.timezone(), "Timezone": dataObject.timezone(),
"Locale": dateFormatter.locale.identifier, "Locale": dateFormatter.locale.identifier
] as [String: Any] ] as [String: Any]
Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil") Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil")
return CLEmptyString return CLEmptyString
} }
let timeDifference = local.timeAgo(since: timezoneDate) let timeDifference = local.timeAgo(since: timezoneDate)
if timeDifference.contains("Just now") { if timeDifference.contains("Just now") {
@ -310,12 +310,12 @@ extension TimezoneDataOperations {
return date return date
} }
private func localDate() -> String { private func localDate() -> String {
let dateFormatter = DateFormatterManager.dateFormatter(with: .medium, for: TimeZone.autoupdatingCurrent.identifier) let dateFormatter = DateFormatterManager.dateFormatter(with: .medium, for: TimeZone.autoupdatingCurrent.identifier)
return dateFormatter.string(from: Date()) return dateFormatter.string(from: Date())
} }
private func localeDate(with format: String) -> String { private func localeDate(with format: String) -> String {
let dateFormatter = DateFormatterManager.localizedFormatter(with: format, for: TimeZone.autoupdatingCurrent.identifier) let dateFormatter = DateFormatterManager.localizedFormatter(with: format, for: TimeZone.autoupdatingCurrent.identifier)
return dateFormatter.string(from: Date()) return dateFormatter.string(from: Date())

38
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -3,12 +3,12 @@
import Cocoa import Cocoa
class NotesPopover: NSViewController { class NotesPopover: NSViewController {
private enum OverrideType { private enum OverrideType {
case timezoneFormat case timezoneFormat
case seconds case seconds
} }
var dataObject: TimezoneData? var dataObject: TimezoneData?
var timezoneObjects: [Data]? var timezoneObjects: [Data]?
@ -38,7 +38,7 @@ class NotesPopover: NSViewController {
@IBOutlet var timeFormatControl: NSSegmentedControl! @IBOutlet var timeFormatControl: NSSegmentedControl!
@IBOutlet weak var secondsFormatControl: NSSegmentedControl! @IBOutlet weak var secondsFormatControl: NSSegmentedControl!
@IBOutlet var notesTextView: TextViewWithPlaceholder! @IBOutlet var notesTextView: TextViewWithPlaceholder!
override func viewDidLoad() { override func viewDidLoad() {
@ -62,7 +62,7 @@ class NotesPopover: NSViewController {
"1 hour before", "1 hour before",
"2 hour before", "2 hour before",
"1 day before", "1 day before",
"2 days before", "2 days before"
] ]
alertPopupButton.removeAllItems() alertPopupButton.removeAllItems()
@ -200,7 +200,7 @@ class NotesPopover: NSViewController {
let attributesDictionary = [ let attributesDictionary = [
NSAttributedString.Key.font: font, NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.paragraphStyle: style
] ]
button.attributedTitle = NSAttributedString(string: title, button.attributedTitle = NSAttributedString(string: title,
@ -268,7 +268,7 @@ class NotesPopover: NSViewController {
updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat) updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat)
updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat) updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat)
refreshMainTableView() refreshMainTableView()
// Update the display if the chosen menubar mode is compact! // Update the display if the chosen menubar mode is compact!
if let delegate = NSApplication.shared.delegate as? AppDelegate { if let delegate = NSApplication.shared.delegate as? AppDelegate {
let handler = delegate.statusItemForPanel() let handler = delegate.statusItemForPanel()
@ -288,24 +288,24 @@ class NotesPopover: NSViewController {
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 == dataObject return convertedObject == 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)
} }
} }
@ -452,19 +452,19 @@ class NotesPopover: NSViewController {
updateContent() updateContent()
} }
} }
@IBAction func customizeSecondsFormat(_ sender: NSSegmentedControl) { @IBAction func customizeSecondsFormat(_ sender: NSSegmentedControl) {
updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds) updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds)
updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds) updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds)
refreshMainTableView() refreshMainTableView()
// Update the display if the chosen menubar mode is compact! // Update the display if the chosen menubar mode is compact!
if let delegate = NSApplication.shared.delegate as? AppDelegate { if let delegate = NSApplication.shared.delegate as? AppDelegate {
let handler = delegate.statusItemForPanel() let handler = delegate.statusItemForPanel()
handler.setupStatusItem() handler.setupStatusItem()
} }
} }
} }
@objc extension NotesPopover { @objc extension NotesPopover {
@ -513,7 +513,7 @@ class NotesPopover: NSViewController {
setInitialReminderTime() setInitialReminderTime()
updateTimeFormat() updateTimeFormat()
updateSecondsFormat() updateSecondsFormat()
} }
@ -526,7 +526,7 @@ class NotesPopover: NSViewController {
timeFormatControl.setSelected(true, forSegment: 2) timeFormatControl.setSelected(true, forSegment: 2)
} }
} }
private func updateSecondsFormat() { private func updateSecondsFormat() {
if dataObject?.overrideSecondsFormat.rawValue == 0 { if dataObject?.overrideSecondsFormat.rawValue == 0 {
secondsFormatControl.setSelected(true, forSegment: 0) secondsFormatControl.setSelected(true, forSegment: 0)

2
Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift

@ -18,7 +18,7 @@ class TextViewWithPlaceholder: NSTextView {
if let placeHolderFont = NSFont(name: "Avenir", size: 14) { if let placeHolderFont = NSFont(name: "Avenir", size: 14) {
let textDict = [ let textDict = [
NSAttributedString.Key.foregroundColor: NSColor.gray, NSAttributedString.Key.foregroundColor: NSColor.gray,
NSAttributedString.Key.font: placeHolderFont, NSAttributedString.Key.font: placeHolderFont
] ]
return NSAttributedString(string: " Add your notes here.", attributes: textDict) return NSAttributedString(string: " Add your notes here.", attributes: textDict)
} }

32
Clocker/Panel/PanelController.swift

@ -42,20 +42,20 @@ class PanelController: ParentPanelController {
@objc override func updateDefaultPreferences() { @objc override func updateDefaultPreferences() {
super.updateDefaultPreferences() super.updateDefaultPreferences()
} }
func setFrameTheNewWay(_ rect: NSRect, _ maxX: CGFloat) { func setFrameTheNewWay(_ rect: NSRect, _ maxX: CGFloat) {
// Calculate window's top left point. // Calculate window's top left point.
// First, center window under status item. // First, center window under status item.
let w = CGFloat(NSWidth((window?.frame)!)) let w = CGFloat(NSWidth((window?.frame)!))
var x = CGFloat(roundf(Float(NSMidX(rect) - w / 2))) var x = CGFloat(roundf(Float(rect.midX - w / 2)))
let y = CGFloat(NSMinY(rect) - 2) let y = CGFloat(rect.minY - 2)
let kMinimumSpaceBetweenWindowAndScreenEdge: CGFloat = 10 let kMinimumSpaceBetweenWindowAndScreenEdge: CGFloat = 10
if x + w + kMinimumSpaceBetweenWindowAndScreenEdge > maxX { if x + w + kMinimumSpaceBetweenWindowAndScreenEdge > maxX {
x = maxX - w - kMinimumSpaceBetweenWindowAndScreenEdge x = maxX - w - kMinimumSpaceBetweenWindowAndScreenEdge
} }
window?.setFrameTopLeftPoint(NSMakePoint(x, y)) window?.setFrameTopLeftPoint(NSPoint(x: x, y: y))
window?.invalidateShadow() window?.invalidateShadow()
} }
@ -99,20 +99,20 @@ class PanelController: ParentPanelController {
// New way to set the panel's frame. // New way to set the panel's frame.
// This takes into account the screen's dimensions. // This takes into account the screen's dimensions.
private func setPanelFrame() { private func setPanelFrame() {
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else {
return return
} }
var statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.view?.window var statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.view?.window
var statusView = appDelegate.statusItemForPanel().statusItem.view var statusView = appDelegate.statusItemForPanel().statusItem.view
// This below is a better way than actually checking if the menubar compact mode is set. // This below is a better way than actually checking if the menubar compact mode is set.
if statusBackgroundWindow == nil || statusView == nil { if statusBackgroundWindow == nil || statusView == nil {
statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.button?.window statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.button?.window
statusView = appDelegate.statusItemForPanel().statusItem.button statusView = appDelegate.statusItemForPanel().statusItem.button
} }
if let statusWindow = statusBackgroundWindow, if let statusWindow = statusBackgroundWindow,
let statusButton = statusView { let statusButton = statusView {
var statusItemFrame = statusWindow.convertToScreen(statusButton.frame) var statusItemFrame = statusWindow.convertToScreen(statusButton.frame)
@ -175,16 +175,16 @@ class PanelController: ParentPanelController {
"Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No", "Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No",
"Country": country, "Country": country,
"Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No", "Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No",
"Number of Timezones": preferences.count, "Number of Timezones": preferences.count
] ]
Logger.log(object: panelEvent, for: "openedPanel") Logger.log(object: panelEvent, for: "openedPanel")
} }
private func startWindowTimer() { private func startWindowTimer() {
stopMenubarTimerIfNeccesary() stopMenubarTimerIfNeccesary()
if let timer = parentTimer, timer.state == .paused { if let timer = parentTimer, timer.state == .paused {
parentTimer?.start() parentTimer?.start()
return return
@ -194,9 +194,9 @@ class PanelController: ParentPanelController {
} }
private func startTimer() { private func startTimer() {
print("Start timer called") print("Start timer called")
parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.updateTime() self.updateTime()
@ -205,14 +205,14 @@ class PanelController: ParentPanelController {
parentTimer!.start() parentTimer!.start()
} }
private func stopMenubarTimerIfNeccesary() { private func stopMenubarTimerIfNeccesary() {
let count = (DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data])?.count ?? 0 let count = (DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data])?.count ?? 0
if count >= 1 || DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if count >= 1 || DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
if let delegate = NSApplication.shared.delegate as? AppDelegate { if let delegate = NSApplication.shared.delegate as? AppDelegate {
print("\nWe will be invalidating the menubar timer as we want the parent timer to take care of both panel and menubar ") print("\nWe will be invalidating the menubar timer as we want the parent timer to take care of both panel and menubar ")
delegate.invalidateMenubarTimer(false) delegate.invalidateMenubarTimer(false)
} }
} }

22
Clocker/Panel/ParentPanelController.swift

@ -27,7 +27,7 @@ class ParentPanelController: NSWindowController {
var morePopover: NSPopover? var morePopover: NSPopover?
var datasource: TimezoneDataSource? var datasource: TimezoneDataSource?
private lazy var feedbackWindow: AppFeedbackWindowController = AppFeedbackWindowController.shared() private lazy var feedbackWindow: AppFeedbackWindowController = AppFeedbackWindowController.shared()
private var note: NotesPopover? private var note: NotesPopover?
@ -71,7 +71,7 @@ class ParentPanelController: NSWindowController {
@IBOutlet var calendarButton: NSButton! @IBOutlet var calendarButton: NSButton!
@IBOutlet var sliderDatePicker: NSDatePicker! @IBOutlet var sliderDatePicker: NSDatePicker!
@IBOutlet var debugVersionView: NSView! @IBOutlet var debugVersionView: NSView!
var defaultPreferences: [Data] { var defaultPreferences: [Data] {
@ -132,17 +132,17 @@ class ParentPanelController: NSWindowController {
selector: #selector(timezoneGonnaChange), selector: #selector(timezoneGonnaChange),
name: NSNotification.Name.NSSystemTimeZoneDidChange, name: NSNotification.Name.NSSystemTimeZoneDidChange,
object: nil) object: nil)
showDebugVersionViewIfNeccesary() showDebugVersionViewIfNeccesary()
} }
private func showDebugVersionViewIfNeccesary() { private func showDebugVersionViewIfNeccesary() {
if debugVersionView != nil { if debugVersionView != nil {
debugVersionView.wantsLayer = true debugVersionView.wantsLayer = true
debugVersionView.layer?.backgroundColor = NSColor.systemRed.cgColor debugVersionView.layer?.backgroundColor = NSColor.systemRed.cgColor
} }
#if RELEASE #if RELEASE
if debugVersionView != nil && stackView.arrangedSubviews.contains(debugVersionView) { if debugVersionView != nil && stackView.arrangedSubviews.contains(debugVersionView) {
stackView.removeView(debugVersionView) stackView.removeView(debugVersionView)
@ -248,7 +248,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13), NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13)
] ]
let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes) let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes)
@ -562,13 +562,13 @@ class ParentPanelController: NSWindowController {
target.image = Themer.shared().extraOptionsHighlightedImage() target.image = Themer.shared().extraOptionsHighlightedImage()
popover.animates = true popover.animates = true
if note == nil { if note == nil {
note = NotesPopover(nibName: NSNib.Name.notesPopover, bundle: nil) note = NotesPopover(nibName: NSNib.Name.notesPopover, bundle: nil)
popover.behavior = .applicationDefined popover.behavior = .applicationDefined
popover.delegate = self popover.delegate = self
} }
// Found a case where row number was 8 but we had only 2 timezones // Found a case where row number was 8 but we had only 2 timezones
if correctRow >= defaults.count { if correctRow >= defaults.count {
correctRow = defaults.count - 1 correctRow = defaults.count - 1
@ -812,7 +812,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
] ]
leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes) leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes)
rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes) rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes)
@ -842,7 +842,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
] ]
if self.leftButton.attributedTitle.string == "Not Really" { if self.leftButton.attributedTitle.string == "Not Really" {

26
Clocker/Panel/Rate Controller/RateController.swift

@ -3,53 +3,53 @@
import Cocoa import Cocoa
final class RateController { final class RateController {
private static var storage = UserDefaults.standard private static var storage = UserDefaults.standard
private static let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A" private static let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A"
private static var debugging = false private static var debugging = false
private enum Keys { private enum Keys {
static let lastPrompt = "last-prompt" static let lastPrompt = "last-prompt"
static let lastVersion = "last-version" static let lastVersion = "last-version"
static let install = "install" static let install = "install"
} }
class func applicationDidLaunch(_ defaults: UserDefaults = UserDefaults.standard) { class func applicationDidLaunch(_ defaults: UserDefaults = UserDefaults.standard) {
if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) { if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) {
debugging = true debugging = true
} }
storage = defaults storage = defaults
guard defaults.object(forKey: Keys.install) == nil else { return } guard defaults.object(forKey: Keys.install) == nil else { return }
defaults.set(Date(), forKey: Keys.install) defaults.set(Date(), forKey: Keys.install)
} }
class func setPreviewMode(_ value: Bool) { class func setPreviewMode(_ value: Bool) {
debugging = value debugging = value
} }
class func prompted() { class func prompted() {
storage.set(Date(), forKey: Keys.lastPrompt) storage.set(Date(), forKey: Keys.lastPrompt)
storage.set(version, forKey: Keys.lastVersion) storage.set(version, forKey: Keys.lastVersion)
} }
class func canPrompt() -> Bool { class func canPrompt() -> Bool {
guard debugging == false else { return true } guard debugging == false else { return true }
let day: TimeInterval = -1 * 60 * 60 * 24 let day: TimeInterval = -1 * 60 * 60 * 24
let minInstall: TimeInterval = day * 7 let minInstall: TimeInterval = day * 7
// Check if the app has been installed for atleast 7 days // Check if the app has been installed for atleast 7 days
guard let install = storage.object(forKey: Keys.install) as? Date, guard let install = storage.object(forKey: Keys.install) as? Date,
install.timeIntervalSinceNow < minInstall install.timeIntervalSinceNow < minInstall
else { return false } else { return false }
// If we have never been prompted before, go ahead and prompt // If we have never been prompted before, go ahead and prompt
guard let lastPrompt = storage.object(forKey: Keys.lastPrompt) as? Date, guard let lastPrompt = storage.object(forKey: Keys.lastPrompt) as? Date,
let lastVersion = storage.object(forKey: Keys.lastVersion) as? String let lastVersion = storage.object(forKey: Keys.lastVersion) as? String
else { return true } else { return true }
// Minimum interval between two versions should be 45 // Minimum interval between two versions should be 45
let minInterval: TimeInterval = day * 45 let minInterval: TimeInterval = day * 45
@ -58,7 +58,7 @@ final class RateController {
// limit all types of prompts to at least 1mo intervals // limit all types of prompts to at least 1mo intervals
&& lastPrompt.timeIntervalSinceNow < minInterval && lastPrompt.timeIntervalSinceNow < minInterval
} }
class func prompt() { class func prompt() {
guard let ratingsURL = URL(string: AboutUsConstants.AppStoreLink) else { guard let ratingsURL = URL(string: AboutUsConstants.AppStoreLink) else {
return return

2
Clocker/Panel/UI/PanelTableView.swift

@ -31,7 +31,7 @@ class PanelTableView: NSTableView {
let options: NSTrackingArea.Options = [ let options: NSTrackingArea.Options = [
.mouseMoved, .mouseMoved,
.mouseEnteredAndExited, .mouseEnteredAndExited,
.activeAlways, .activeAlways
] ]
let clipRect = enclosingScrollView?.contentView.bounds ?? .zero let clipRect = enclosingScrollView?.contentView.bounds ?? .zero

4
Clocker/Panel/UI/TimezoneCellView.swift

@ -150,14 +150,14 @@ class TimezoneCellView: NSTableCellView {
relativeTo: superview?.convert(bounds, to: nil) ?? relativeRect, relativeTo: superview?.convert(bounds, to: nil) ?? relativeRect,
andButton: sender) andButton: sender)
} }
Logger.log(object: [:], for: "Open Extra Options") Logger.log(object: [:], for: "Open Extra Options")
} }
override func mouseDown(with _: NSEvent) { override func mouseDown(with _: NSEvent) {
window?.endEditing(for: nil) window?.endEditing(for: nil)
} }
override func rightMouseDown(with event: NSEvent) { override func rightMouseDown(with event: NSEvent) {
super.rightMouseDown(with: event) super.rightMouseDown(with: event)
showExtraOptions(extraOptions) showExtraOptions(extraOptions)

22
Clocker/Preferences/About/AboutViewController.swift

@ -29,10 +29,10 @@ class AboutViewController: ParentViewController {
let longVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "N/A" let longVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "N/A"
versionField.stringValue = "Clocker \(shortVersion) (\(longVersion))" versionField.stringValue = "Clocker \(shortVersion) (\(longVersion))"
setup() setup()
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in
self.setup() self.setup()
} }
} }
@ -49,12 +49,12 @@ class AboutViewController: ParentViewController {
supportClocker, supportClocker,
openSourceButton openSourceButton
] ]
let localizedKeys = ["1. @n0shake on Twitter for quick comments", let localizedKeys = ["1. @n0shake on Twitter for quick comments",
"2. For Private Feedback", "2. For Private Feedback",
"You can support Clocker by leaving a review on the App Store! :)", "You can support Clocker by leaving a review on the App Store! :)",
"Clocker is Open Source. You can check out the source code here."] "Clocker is Open Source. You can check out the source code here."]
zip(buttonsInOrder, localizedKeys).forEach { arg in zip(buttonsInOrder, localizedKeys).forEach { arg in
let (button, title) = arg let (button, title) = arg
button?.title = title button?.title = title
@ -68,7 +68,7 @@ class AboutViewController: ParentViewController {
private func setUnderline(for button: UnderlinedButton?, range: NSRange) { private func setUnderline(for button: UnderlinedButton?, range: NSRange) {
guard let underlinedButton = button else { return } guard let underlinedButton = button else { return }
let mutableParaghStyle = NSMutableParagraphStyle() let mutableParaghStyle = NSMutableParagraphStyle()
mutableParaghStyle.alignment = .center mutableParaghStyle.alignment = .center
@ -78,13 +78,13 @@ class AboutViewController: ParentViewController {
range: range) range: range)
originalText.addAttribute(NSAttributedString.Key.foregroundColor, originalText.addAttribute(NSAttributedString.Key.foregroundColor,
value: Themer.shared().mainTextColor(), value: Themer.shared().mainTextColor(),
range: NSMakeRange(0, underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
originalText.addAttribute(NSAttributedString.Key.font, originalText.addAttribute(NSAttributedString.Key.font,
value: (button?.font)! , value: (button?.font)! ,
range: NSMakeRange(0, underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
originalText.addAttribute(NSAttributedString.Key.paragraphStyle, originalText.addAttribute(NSAttributedString.Key.paragraphStyle,
value: mutableParaghStyle , value: mutableParaghStyle ,
range: NSMakeRange(0, underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
underlinedButton.attributedTitle = originalText underlinedButton.attributedTitle = originalText
} }
@ -131,9 +131,9 @@ class AboutViewController: ParentViewController {
let custom: [String: Any] = ["Country": countryCode] let custom: [String: Any] = ["Country": countryCode]
Logger.log(object: custom, for: "Opened GitHub") Logger.log(object: custom, for: "Opened GitHub")
} }
@IBOutlet weak var feedbackLabel: NSTextField! @IBOutlet weak var feedbackLabel: NSTextField!
private func setup() { private func setup() {
feedbackLabel.stringValue = "Feedback is always welcome:" feedbackLabel.stringValue = "Feedback is always welcome:"
feedbackLabel.textColor = Themer.shared().mainTextColor() feedbackLabel.textColor = Themer.shared().mainTextColor()

2
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

@ -146,7 +146,7 @@ class AppFeedbackWindowController: NSWindowController {
AppFeedbackConstants.CLAppFeedbackEmailProperty: email, AppFeedbackConstants.CLAppFeedbackEmailProperty: email,
AppFeedbackConstants.CLAppFeedbackFeedbackProperty: appFeedbackProperty, AppFeedbackConstants.CLAppFeedbackFeedbackProperty: appFeedbackProperty,
AppFeedbackConstants.CLOperatingSystemVersion: osVersion, AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo, AppFeedbackConstants.CLClockerVersion: versionInfo
] ]
return feedbackInfo return feedbackInfo

57
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -12,7 +12,7 @@ class AppearanceViewController: ParentViewController {
@IBOutlet weak var includeDayInMenubarControl: NSSegmentedControl! @IBOutlet weak var includeDayInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includeDateInMenubarControl: NSSegmentedControl! @IBOutlet weak var includeDateInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includePlaceNameControl: NSSegmentedControl! @IBOutlet weak var includePlaceNameControl: NSSegmentedControl!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -31,16 +31,16 @@ class AppearanceViewController: ParentViewController {
"6 days", "6 days",
"7 days" "7 days"
]) ])
setup() setup()
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in
self.setup() self.setup()
self.animateBackgroundColorChange() self.animateBackgroundColorChange()
self.view.needsDisplay = true // Let's make the color change permanent. self.view.needsDisplay = true // Let's make the color change permanent.
} }
} }
private func animateBackgroundColorChange() { private func animateBackgroundColorChange() {
let colorAnimation = CABasicAnimation(keyPath: "backgroundColor") let colorAnimation = CABasicAnimation(keyPath: "backgroundColor")
colorAnimation.duration = 0.25 colorAnimation.duration = 0.25
@ -60,18 +60,18 @@ class AppearanceViewController: ParentViewController {
if let selectedIndex = DataStore.shared().retrieve(key: CLFutureSliderRange) as? NSNumber { if let selectedIndex = DataStore.shared().retrieve(key: CLFutureSliderRange) as? NSNumber {
sliderDayRangePopup.selectItem(at: selectedIndex.intValue) sliderDayRangePopup.selectItem(at: selectedIndex.intValue)
} }
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
theme.setEnabled(true, forSegment: 2) theme.setEnabled(true, forSegment: 2)
} else { } else {
theme.setEnabled(false, forSegment: 2) theme.setEnabled(false, forSegment: 2)
} }
let shouldDisplayCompact = DataStore.shared().shouldDisplay(.menubarCompactMode) let shouldDisplayCompact = DataStore.shared().shouldDisplay(.menubarCompactMode)
menubarMode.setSelected(true, forSegment: shouldDisplayCompact ? 0 : 1) menubarMode.setSelected(true, forSegment: shouldDisplayCompact ? 0 : 1)
updateMenubarControls(!shouldDisplayCompact) updateMenubarControls(!shouldDisplayCompact)
} }
@IBOutlet weak var headerLabel: NSTextField! @IBOutlet weak var headerLabel: NSTextField!
@IBOutlet weak var timeFormatLabel: NSTextField! @IBOutlet weak var timeFormatLabel: NSTextField!
@IBOutlet weak var panelTheme: NSTextField! @IBOutlet weak var panelTheme: NSTextField!
@ -87,7 +87,7 @@ class AppearanceViewController: ParentViewController {
@IBOutlet weak var menubarDisplayOptionsLabel: NSTextField! @IBOutlet weak var menubarDisplayOptionsLabel: NSTextField!
@IBOutlet weak var appDisplayLabel: NSTextField! @IBOutlet weak var appDisplayLabel: NSTextField!
@IBOutlet weak var menubarModeLabel: NSTextField! @IBOutlet weak var menubarModeLabel: NSTextField!
private func setup() { private func setup() {
headerLabel.stringValue = "Main Panel Options" headerLabel.stringValue = "Main Panel Options"
timeFormatLabel.stringValue = "Time Format" timeFormatLabel.stringValue = "Time Format"
@ -103,7 +103,7 @@ class AppearanceViewController: ParentViewController {
includePlaceLabel.stringValue = "Include Place Name" includePlaceLabel.stringValue = "Include Place Name"
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()
} }
@ -117,16 +117,16 @@ class AppearanceViewController: ParentViewController {
Logger.log(object: ["Time Format": sender.selectedSegment == 0 ? "12 Hour Format" : "24 Hour Format"], for: "Time Format Selected") Logger.log(object: ["Time Format": sender.selectedSegment == 0 ? "12 Hour Format" : "24 Hour Format"], for: "Time Format Selected")
refresh(panel: true, floating: true) refresh(panel: true, floating: true)
updateStatusItem() updateStatusItem()
} }
private var previousBackgroundColor: NSColor = NSColor.white private var previousBackgroundColor: NSColor = NSColor.white
@IBAction func themeChanged(_ sender: NSSegmentedControl) { @IBAction func themeChanged(_ sender: NSSegmentedControl) {
previousBackgroundColor = Themer.shared().mainBackgroundColor() previousBackgroundColor = Themer.shared().mainBackgroundColor()
Themer.shared().set(theme: sender.selectedSegment) Themer.shared().set(theme: sender.selectedSegment)
refresh(panel: false, floating: true) refresh(panel: false, floating: true)
@ -194,9 +194,9 @@ class AppearanceViewController: ParentViewController {
updateStatusItem() updateStatusItem()
} }
@IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) { @IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) {
if sender.selectedSegment == 0 { if sender.selectedSegment == 0 {
Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode") Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode")
NSApp.setActivationPolicy(.accessory) NSApp.setActivationPolicy(.accessory)
@ -205,7 +205,6 @@ class AppearanceViewController: ParentViewController {
NSApp.setActivationPolicy(.regular) NSApp.setActivationPolicy(.regular)
} }
} }
private func refresh(panel: Bool, floating: Bool) { private func refresh(panel: Bool, floating: Bool) {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
@ -234,54 +233,52 @@ class AppearanceViewController: ParentViewController {
} }
} }
} }
@IBAction func displayDayInMenubarAction(_ sender: Any) { @IBAction func displayDayInMenubarAction(_ sender: Any) {
DataStore.shared().updateDayPreference() DataStore.shared().updateDayPreference()
updateStatusItem() updateStatusItem()
} }
@IBAction func displayDateInMenubarAction(_ sender: Any) { @IBAction func displayDateInMenubarAction(_ sender: Any) {
updateStatusItem() updateStatusItem()
} }
@IBAction func displayPlaceInMenubarAction(_ sender: Any) { @IBAction func displayPlaceInMenubarAction(_ sender: Any) {
updateStatusItem() updateStatusItem()
} }
private func updateStatusItem() { private func updateStatusItem() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return return
} }
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if DataStore.shared().shouldDisplay(.menubarCompactMode) {
statusItem.setupStatusItem() statusItem.setupStatusItem()
} else { } else {
statusItem.performTimerWork() statusItem.performTimerWork()
} }
} }
@IBAction func menubarModeChanged(_ sender: NSSegmentedControl) { @IBAction func menubarModeChanged(_ sender: NSSegmentedControl) {
updateMenubarControls(sender.selectedSegment == 1) updateMenubarControls(sender.selectedSegment == 1)
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return return
} }
statusItem.setupStatusItem() statusItem.setupStatusItem()
if sender.selectedSegment == 0 { if sender.selectedSegment == 0 {
Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Compact Mode") Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Compact Mode")
} else { } else {
Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Standard Mode") Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Standard Mode")
} }
} }
// We don't support showing day or date in the menubar for compact mode yet. // We don't support showing day or date in the menubar for compact mode yet.
// Disable those options to let the user know. // Disable those options to let the user know.
private func updateMenubarControls(_ isEnabled: Bool) { private func updateMenubarControls(_ isEnabled: Bool) {
[includePlaceNameControl, includeDateInMenubarControl].forEach{ $0?.isEnabled = isEnabled } [includePlaceNameControl, includeDateInMenubarControl].forEach { $0?.isEnabled = isEnabled }
} }
} }

95
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -4,18 +4,18 @@ import Cocoa
import EventKit import EventKit
class ClockerTextBackgroundView: NSView { class ClockerTextBackgroundView: NSView {
override func awakeFromNib() { override func awakeFromNib() {
wantsLayer = true wantsLayer = true
layer?.cornerRadius = 8.0 layer?.cornerRadius = 8.0
layer?.masksToBounds = false layer?.masksToBounds = false
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in
self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
} }
} }
override func updateLayer() { override func updateLayer() {
super.updateLayer() super.updateLayer()
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
@ -23,7 +23,7 @@ class ClockerTextBackgroundView: NSView {
} }
class CalendarViewController: ParentViewController { class CalendarViewController: ParentViewController {
@IBOutlet var showSegmentedControl: NSSegmentedControl! @IBOutlet var showSegmentedControl: NSSegmentedControl!
@IBOutlet var allDaysSegmentedControl: NSSegmentedControl! @IBOutlet var allDaysSegmentedControl: NSSegmentedControl!
@IBOutlet var truncateTextField: NSTextField! @IBOutlet var truncateTextField: NSTextField!
@ -31,35 +31,35 @@ class CalendarViewController: ParentViewController {
@IBOutlet var informationField: NSTextField! @IBOutlet var informationField: NSTextField!
@IBOutlet var grantAccessButton: NSButton! @IBOutlet var grantAccessButton: NSButton!
@IBOutlet weak var calendarsTableView: NSTableView! @IBOutlet weak var calendarsTableView: NSTableView!
@IBOutlet weak var showNextMeetingInMenubarControl: NSSegmentedControl! @IBOutlet weak var showNextMeetingInMenubarControl: NSSegmentedControl!
@IBOutlet weak var backgroundView: NSView! @IBOutlet weak var backgroundView: NSView!
@IBOutlet weak var nextMeetingBackgroundView: NSView! @IBOutlet weak var nextMeetingBackgroundView: NSView!
private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars() private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setup() setup()
NotificationCenter.default.addObserver(self, NotificationCenter.default.addObserver(self,
selector: #selector(calendarAccessStatusChanged), selector: #selector(calendarAccessStatusChanged),
name: .calendarAccessGranted, name: .calendarAccessGranted,
object: nil) object: nil)
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in
self.setup() self.setup()
} }
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
noAccessView.material = .underWindowBackground noAccessView.material = .underWindowBackground
} }
} }
@objc func calendarAccessStatusChanged() { @objc func calendarAccessStatusChanged() {
verifyCalendarAccess() verifyCalendarAccess()
view.window?.windowController?.showWindow(nil) view.window?.windowController?.showWindow(nil)
view.window?.makeKeyAndOrderFront(nil) view.window?.makeKeyAndOrderFront(nil)
} }
@ -74,7 +74,7 @@ class CalendarViewController: ParentViewController {
} else { } else {
showSegmentedControl.selectedSegment = 1 showSegmentedControl.selectedSegment = 1
} }
// If the menubar mode is compact, we can't show meetings in the menubar. So disable toggling that option. // If the menubar mode is compact, we can't show meetings in the menubar. So disable toggling that option.
showNextMeetingInMenubarControl.isEnabled = !(DataStore.shared().shouldDisplay(.menubarCompactMode)) showNextMeetingInMenubarControl.isEnabled = !(DataStore.shared().shouldDisplay(.menubarCompactMode))
} }
@ -129,32 +129,32 @@ class CalendarViewController: ParentViewController {
NSWorkspace.shared.launchApplication("System Preferences") NSWorkspace.shared.launchApplication("System Preferences")
} }
} }
@IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) { @IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) {
// We need to start the menubar timer if it hasn't been started already // We need to start the menubar timer if it hasn't been started already
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { guard let delegate = NSApplication.shared.delegate as? AppDelegate else {
assertionFailure() assertionFailure()
return return
} }
let statusItemHandler = delegate.statusItemForPanel() let statusItemHandler = delegate.statusItemForPanel()
if sender.selectedSegment == 0 { if sender.selectedSegment == 0 {
if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true { if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true {
print("Timer is already in progress") print("Timer is already in progress")
updateStatusItem() updateStatusItem()
return return
} }
} else { } else {
statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false) statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false)
} }
} }
@IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) { @IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) {
var showUpcomingEventView = "YES" var showUpcomingEventView = "YES"
@ -179,15 +179,15 @@ class CalendarViewController: ParentViewController {
Logger.log(object: ["Show": "YES"], for: "Upcoming Event View") Logger.log(object: ["Show": "YES"], for: "Upcoming Event View")
} }
} }
private func updateStatusItem() { private func updateStatusItem() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return return
} }
statusItem.performTimerWork() statusItem.performTimerWork()
} }
@IBOutlet weak var headerLabel: NSTextField! @IBOutlet weak var headerLabel: NSTextField!
@IBOutlet weak var upcomingEventView: NSTextField! @IBOutlet weak var upcomingEventView: NSTextField!
@IBOutlet weak var allDayMeetingsLabel: NSTextField! @IBOutlet weak var allDayMeetingsLabel: NSTextField!
@ -197,7 +197,7 @@ class CalendarViewController: ParentViewController {
@IBOutlet weak var showEventsFromLabel: NSTextField! @IBOutlet weak var showEventsFromLabel: NSTextField!
@IBOutlet weak var charactersField: NSTextField! @IBOutlet weak var charactersField: NSTextField!
@IBOutlet weak var truncateAccessoryLabel: NSTextField! @IBOutlet weak var truncateAccessoryLabel: NSTextField!
private func setup() { private func setup() {
// Grant access button's text color is taken care above. // Grant access button's text color is taken care above.
headerLabel.stringValue = "Upcoming Event View Options" headerLabel.stringValue = "Upcoming Event View Options"
@ -208,7 +208,7 @@ class CalendarViewController: ParentViewController {
charactersField.stringValue = "characters" charactersField.stringValue = "characters"
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() }
} }
} }
@ -222,28 +222,27 @@ extension CalendarViewController: NSTableViewDataSource {
} }
extension CalendarViewController: NSTableViewDelegate { extension CalendarViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return false return false
} }
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else { guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else {
return 30.0 return 30.0
} }
return 24.0 return 24.0
} }
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
@ -253,16 +252,16 @@ extension CalendarViewController: NSTableViewDelegate {
calendarCell.calendarSelected.action = #selector(calendarSelected(_:)) calendarCell.calendarSelected.action = #selector(calendarSelected(_:))
return calendarCell return calendarCell
} }
return nil return nil
} }
@objc func calendarSelected(_ checkbox: NSButton) { @objc func calendarSelected(_ checkbox: NSButton) {
let currentSelection = checkbox.tag let currentSelection = checkbox.tag
var sourcesAndCalendars = calendars var sourcesAndCalendars = calendars
if var calInfo = sourcesAndCalendars[currentSelection] as? CalendarInfo { if var calInfo = sourcesAndCalendars[currentSelection] as? CalendarInfo {
calInfo.selected = (checkbox.state == .on) calInfo.selected = (checkbox.state == .on)
sourcesAndCalendars[currentSelection] = calInfo sourcesAndCalendars[currentSelection] = calInfo
@ -270,21 +269,21 @@ extension CalendarViewController: NSTableViewDelegate {
updateSelectedCalendars(sourcesAndCalendars) updateSelectedCalendars(sourcesAndCalendars)
} }
private func updateSelectedCalendars(_ selection: [Any]) { private func updateSelectedCalendars(_ selection: [Any]) {
var selectedCalendars: [String] = [] var selectedCalendars: [String] = []
for obj in selection { for obj in selection {
if let calInfo = obj as? CalendarInfo, calInfo.selected { if let calInfo = obj as? CalendarInfo, calInfo.selected {
selectedCalendars.append(calInfo.calendar.calendarIdentifier) selectedCalendars.append(calInfo.calendar.calendarIdentifier)
} }
} }
UserDefaults.standard.set(selectedCalendars, forKey: CLSelectedCalendars) UserDefaults.standard.set(selectedCalendars, forKey: CLSelectedCalendars)
calendars = EventCenter.sharedCenter().fetchSourcesAndCalendars() calendars = EventCenter.sharedCenter().fetchSourcesAndCalendars()
EventCenter.sharedCenter().filterEvents() EventCenter.sharedCenter().filterEvents()
} }
} }
@ -297,5 +296,3 @@ class CalendarTableViewCell: NSTableCellView {
@IBOutlet var calendarName: NSTextField! @IBOutlet var calendarName: NSTextField!
@IBOutlet var calendarSelected: NSButton! @IBOutlet var calendarSelected: NSButton!
} }

62
Clocker/Preferences/General/PreferencesViewController.swift

@ -74,7 +74,7 @@ class PreferencesViewController: ParentViewController {
@IBOutlet private var tableview: NSView! @IBOutlet private var tableview: NSView!
@IBOutlet private var additionalSortOptions: NSView! @IBOutlet private var additionalSortOptions: NSView!
@IBOutlet weak var startAtLoginLabel: NSTextField! @IBOutlet weak var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton! @IBOutlet var startupCheckbox: NSButton!
@IBOutlet var headerLabel: NSTextField! @IBOutlet var headerLabel: NSTextField!
@ -198,7 +198,7 @@ class PreferencesViewController: ParentViewController {
} }
UserDefaults.standard.set(archivedObjects, forKey: CLMenubarFavorites) UserDefaults.standard.set(archivedObjects, forKey: CLMenubarFavorites)
// Update appereance if in compact menubar mode // Update appereance if in compact menubar mode
if let appDelegate = NSApplication.shared.delegate as? AppDelegate { if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer() appDelegate.setupMenubarTimer()
@ -240,7 +240,7 @@ class PreferencesViewController: ParentViewController {
[timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach { [timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach {
$0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [ $0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
]) ])
} }
@ -378,7 +378,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
} else if currentSelection == "Anywhere on Earth" { } else if currentSelection == "Anywhere on Earth" {
return "GMT+12" return "GMT+12"
} }
return NSTimeZone(name: timezoneFilteredArray[row])?.abbreviation ?? "Error" return NSTimeZone(name: timezoneFilteredArray[row])?.abbreviation ?? "Error"
} }
@ -387,7 +387,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if timezoneArray[row] == "UTC" { if timezoneArray[row] == "UTC" {
return "UTC" return "UTC"
} }
if timezoneArray[row] == "Anywhere on Earth" { if timezoneArray[row] == "Anywhere on Earth" {
return "AoE" return "AoE"
} }
@ -410,7 +410,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if selectedTimeZones.count > row { if selectedTimeZones.count > row {
Logger.log(object: [ Logger.log(object: [
"Old Label": dataObject.customLabel ?? "Error", "Old Label": dataObject.customLabel ?? "Error",
"New Label": formattedValue, "New Label": formattedValue
], ],
for: "Custom Label Changed") for: "Custom Label Changed")
@ -423,7 +423,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
Logger.log(object: [ Logger.log(object: [
"MethodName": "SetObjectValue", "MethodName": "SetObjectValue",
"Selected Timezone Count": selectedTimeZones.count, "Selected Timezone Count": selectedTimeZones.count,
"Current Row": row, "Current Row": row
], ],
for: "Error in selected row count") for: "Error in selected row count")
} }
@ -432,11 +432,11 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
insert(timezone: dataObject, at: row) insert(timezone: dataObject, at: row)
if dataObject.isFavourite == 1, let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] { if dataObject.isFavourite == 1, let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] {
var mutableArray = menubarTitles var mutableArray = menubarTitles
let archivedObject = NSKeyedArchiver.archivedData(withRootObject: dataObject) let archivedObject = NSKeyedArchiver.archivedData(withRootObject: dataObject)
mutableArray.append(archivedObject) mutableArray.append(archivedObject)
UserDefaults.standard.set(mutableArray, forKey: CLMenubarFavorites) UserDefaults.standard.set(mutableArray, forKey: CLMenubarFavorites)
if dataObject.customLabel != nil { if dataObject.customLabel != nil {
@ -446,7 +446,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if let appDelegate = NSApplication.shared.delegate as? AppDelegate { if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer() appDelegate.setupMenubarTimer()
} }
if mutableArray.count > 1 { if mutableArray.count > 1 {
showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar()
} }
@ -472,7 +472,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if let appDelegate = NSApplication.shared.delegate as? AppDelegate, let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count <= 0, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { if let appDelegate = NSApplication.shared.delegate as? AppDelegate, let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count <= 0, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false {
appDelegate.invalidateMenubarTimer(true) appDelegate.invalidateMenubarTimer(true)
} }
if let appDelegate = NSApplication.shared.delegate as? AppDelegate { if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer() appDelegate.setupMenubarTimer()
} }
@ -487,43 +487,43 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
} }
private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() { private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() {
let isUITestRunning = ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) let isUITestRunning = ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument)
// If we have seen displayed the message before, abort! // If we have seen displayed the message before, abort!
let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: CLLongStatusBarWarningMessage) let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: CLLongStatusBarWarningMessage)
if haveWeSeenThisMessageBefore && !isUITestRunning { if haveWeSeenThisMessageBefore && !isUITestRunning {
return return
} }
// If the user is already using the compact mode, abort. // If the user is already using the compact mode, abort.
if DataStore.shared().shouldDisplay(.menubarCompactMode) && !isUITestRunning { if DataStore.shared().shouldDisplay(.menubarCompactMode) && !isUITestRunning {
return return
} }
// Time to display the alert. // Time to display the alert.
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
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 = "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.addButton(withTitle: "Enable Compact Mode") alert.addButton(withTitle: "Enable Compact Mode")
alert.addButton(withTitle: "Cancel") alert.addButton(withTitle: "Cancel")
let response = alert.runModal() let response = alert.runModal()
if response.rawValue == 1000 { if response.rawValue == 1000 {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
UserDefaults.standard.set(0, forKey: CLMenubarCompactMode) UserDefaults.standard.set(0, forKey: CLMenubarCompactMode)
if alert.suppressionButton?.state == NSControl.StateValue.on { if alert.suppressionButton?.state == NSControl.StateValue.on {
UserDefaults.standard.set(true, forKey: CLLongStatusBarWarningMessage) UserDefaults.standard.set(true, forKey: CLLongStatusBarWarningMessage)
} }
self.updateStatusBarAppearance() self.updateStatusBarAppearance()
Logger.log(object: ["Context": ">1 Menubar Timezone in Preferences"], for: "Switched to Compact Mode") Logger.log(object: ["Context": ">1 Menubar Timezone in Preferences"], for: "Switched to Compact Mode")
} }
} }
@ -656,7 +656,7 @@ extension PreferencesViewController {
self.dataTask = NetworkManager.task(with: urlString, 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 {
@ -696,7 +696,7 @@ extension PreferencesViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId, CLPlaceIdentifier: result.placeId
] as [String: Any] ] as [String: Any]
self.filteredArray.append(TimezoneData(with: totalPackage)) self.filteredArray.append(TimezoneData(with: totalPackage))
@ -765,7 +765,7 @@ extension PreferencesViewController {
let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)&timestamp=\(timeStamp)&key=\(CLGeocodingKey)" let urlString = "https://maps.googleapis.com/maps/api/timezone/json?location=\(tuple)&timestamp=\(timeStamp)&key=\(CLGeocodingKey)"
NetworkManager.task(with: urlString) { [weak self] response, error in NetworkManager.task(with: urlString) { [weak self] response, error in
guard let `self` = self else { return } guard let `self` = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
@ -793,7 +793,7 @@ extension PreferencesViewController {
"latitude": dataObject.latitude!, "latitude": dataObject.latitude!,
"longitude": dataObject.longitude!, "longitude": dataObject.longitude!,
"nextUpdate": CLEmptyString, "nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress, CLCustomLabel: filteredAddress
] as [String: Any] ] as [String: Any]
let timezoneObject = TimezoneData(with: newTimeZone) let timezoneObject = TimezoneData(with: newTimeZone)
@ -951,7 +951,7 @@ extension PreferencesViewController {
} }
let currentSelection = timezoneFilteredArray[availableTimezoneTableView.selectedRow] let currentSelection = timezoneFilteredArray[availableTimezoneTableView.selectedRow]
let metaInfo = metadata(for: currentSelection) let metaInfo = metadata(for: currentSelection)
data.timezoneID = metaInfo.0 data.timezoneID = metaInfo.0
data.formattedAddress = metaInfo.1 data.formattedAddress = metaInfo.1
@ -992,7 +992,7 @@ 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" {
return ("GMT-1200", selection) return ("GMT-1200", selection)
@ -1072,7 +1072,7 @@ extension PreferencesViewController {
if selectedTimeZones.count == 0 { if selectedTimeZones.count == 0 {
UserDefaults.standard.set(nil, forKey: CLMenubarFavorites) UserDefaults.standard.set(nil, forKey: CLMenubarFavorites)
} }
updateStatusBarAppearance() updateStatusBarAppearance()
updateStatusItem() updateStatusItem()
@ -1086,12 +1086,12 @@ extension PreferencesViewController {
statusItem.performTimerWork() statusItem.performTimerWork()
} }
private func updateStatusBarAppearance() { private func updateStatusBarAppearance() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else { guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return return
} }
statusItem.setupStatusItem() statusItem.setupStatusItem()
} }

40
Clocker/Preferences/OneWindowController.swift

@ -3,35 +3,35 @@
import Cocoa import Cocoa
class CenteredTabViewController: NSTabViewController { class CenteredTabViewController: NSTabViewController {
override func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { override func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
super.toolbarDefaultItemIdentifiers(toolbar) super.toolbarDefaultItemIdentifiers(toolbar)
var toolbarItems: [NSToolbarItem.Identifier] = [NSToolbarItem.Identifier.flexibleSpace] var toolbarItems: [NSToolbarItem.Identifier] = [NSToolbarItem.Identifier.flexibleSpace]
tabViewItems.forEach { (item) in tabViewItems.forEach { (item) in
if let identifier = item.identifier as? String { if let identifier = item.identifier as? String {
toolbarItems.append(NSToolbarItem.Identifier.init(identifier)) toolbarItems.append(NSToolbarItem.Identifier.init(identifier))
} }
} }
toolbarItems.append(NSToolbarItem.Identifier.flexibleSpace) toolbarItems.append(NSToolbarItem.Identifier.flexibleSpace)
return toolbarItems return toolbarItems
} }
} }
class OneWindowController: NSWindowController { class OneWindowController: NSWindowController {
private static var sharedWindow: OneWindowController! private static var sharedWindow: OneWindowController!
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
setup() setup()
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in
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)
@ -41,17 +41,17 @@ class OneWindowController: NSWindowController {
self.setupToolbarImages() self.setupToolbarImages()
} }
} }
private func setup() { private func setup() {
setupWindow() setupWindow()
setupToolbarImages() setupToolbarImages()
} }
private func setupWindow() { private func setupWindow() {
window?.titlebarAppearsTransparent = true window?.titlebarAppearsTransparent = true
window?.backgroundColor = Themer.shared().mainBackgroundColor() window?.backgroundColor = Themer.shared().mainBackgroundColor()
} }
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)
@ -59,33 +59,33 @@ class OneWindowController: NSWindowController {
} }
return sharedWindow return sharedWindow
} }
func openPermissions() { func openPermissions() {
guard let window = window else { guard let window = window else {
return return
} }
if !(window.isMainWindow) || !(window.isVisible) { if !(window.isMainWindow) || !(window.isVisible) {
showWindow(nil) showWindow(nil)
} }
guard let tabViewController = contentViewController as? CenteredTabViewController else { guard let tabViewController = contentViewController as? CenteredTabViewController else {
return return
} }
tabViewController.selectedTabViewItemIndex = 3 tabViewController.selectedTabViewItemIndex = 3
} }
private func setupToolbarImages() { private func setupToolbarImages() {
guard let tabViewController = contentViewController as? CenteredTabViewController else { guard let tabViewController = contentViewController as? CenteredTabViewController else {
return return
} }
let themer = Themer.shared() let themer = Themer.shared()
let identifierTOImageMapping: [String: NSImage] = ["Appearance": themer.appearanceTabImage(), let identifierTOImageMapping: [String: NSImage] = ["Appearance": themer.appearanceTabImage(),
"Calendar": themer.calendarTabImage(), "Calendar": themer.calendarTabImage(),
"Permissions": themer.privacyTabImage()] "Permissions": themer.privacyTabImage()]
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) {

4
Clocker/Preferences/ParentViewController.swift

@ -8,8 +8,8 @@ class ParentViewController: NSViewController {
if let view = view as? ParentView { if let view = view as? ParentView {
view.wantsLayer = true view.wantsLayer = true
} }
preferredContentSize = NSMakeSize(view.frame.size.width, view.frame.size.height) preferredContentSize = NSSize(width: view.frame.size.width, height: view.frame.size.height)
} }
} }

10
Clocker/Preferences/Permissions/PermissionsViewController.swift

@ -3,7 +3,7 @@
import Cocoa import Cocoa
class PermissionsViewController: ParentViewController { class PermissionsViewController: ParentViewController {
@IBOutlet var calendarContainerView: NSView! @IBOutlet var calendarContainerView: NSView!
@IBOutlet var remindersContainerView: NSView! @IBOutlet var remindersContainerView: NSView!
@ -121,10 +121,10 @@ class PermissionsViewController: ParentViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
self.calendarButton.title = "Granted" self.calendarButton.title = "Granted"
// Used to update CalendarViewController's view // Used to update CalendarViewController's view
@ -155,10 +155,10 @@ class PermissionsViewController: ParentViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
self.remindersButton.title = "Granted" self.remindersButton.title = "Granted"
} }
} else { } else {

533
xcodebuild.log

@ -0,0 +1,533 @@
note: Using new build system
note: Planning build
note: Constructing build description
ProcessInfoPlistFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework/Versions/A/Resources/Info.plist /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master/Library/Info.plist (in target: ShortcutRecorder.framework)
cd /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master
builtin-infoPlistUtility /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master/Library/Info.plist -producttype com.apple.product-type.framework -expandbuildsettings -platform macosx -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework/Versions/A/Resources/Info.plist
GenerateDSYMFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework.dSYM /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework/Versions/A/ShortcutRecorder (in target: ShortcutRecorder.framework)
cd /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master
/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework/Versions/A/ShortcutRecorder -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework.dSYM
ProcessProductPackaging "" /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/ClockerHelper.build/ClockerHelper.app.xcent (in target: ClockerHelper)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
Entitlements:
{
"com.apple.security.app-sandbox" = 1;
"com.apple.security.get-task-allow" = 1;
}
builtin-productPackagingUtility -entitlements -format xml -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/ClockerHelper.build/ClockerHelper.app.xcent
ProcessInfoPlistFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app/Contents/Info.plist /Users/ban/Documents/GitHub/Clocker/Clocker/ClockerHelper/Info.plist (in target: ClockerHelper)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-infoPlistUtility /Users/ban/Documents/GitHub/Clocker/Clocker/ClockerHelper/Info.plist -producttype com.apple.product-type.application -genpkginfo /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app/Contents/PkgInfo -expandbuildsettings -platform macosx -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/ClockerHelper.build/Base.lproj/Main-SBPartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/ClockerHelper.build/assetcatalog_generated_info.plist -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app/Contents/Info.plist
CodeSign /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app (in target: ClockerHelper)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
export CODESIGN_ALLOCATE=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
Signing Identity: "-"
/usr/bin/codesign --force --sign - --entitlements /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/ClockerHelper.build/ClockerHelper.app.xcent --timestamp=none /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app
/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app: replacing existing signature
ProcessInfoPlistFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework/Versions/A/Resources/Info.plist /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master/PTHotKey/Info.plist (in target: PTHotKey.framework)
cd /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master
builtin-infoPlistUtility /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master/PTHotKey/Info.plist -producttype com.apple.product-type.framework -expandbuildsettings -platform macosx -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework/Versions/A/Resources/Info.plist
GenerateDSYMFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework.dSYM /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework/Versions/A/PTHotKey (in target: PTHotKey.framework)
cd /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/ShortcutRecorder-master
/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework/Versions/A/PTHotKey -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework.dSYM
ProcessProductPackaging "" /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Clocker.app.xcent (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
Entitlements:
{
"com.apple.security.app-sandbox" = 1;
"com.apple.security.get-task-allow" = 1;
"com.apple.security.network.client" = 1;
"com.apple.security.personal-information.calendars" = 1;
"com.apple.security.temporary-exception.apple-events" = (
"com.apple.reminders"
);
}
builtin-productPackagingUtility -entitlements -format xml -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Clocker.app.xcent
PhaseScriptExecution Run\ Script /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Script-9A4379201BEC220200F4E27F.sh (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
/bin/sh -c /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Script-9A4379201BEC220200F4E27F.sh
PBXCp /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Library/LoginItems/ClockerHelper.app (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-copy -exclude .DS_Store -exclude CVS -exclude .svn -exclude .git -exclude .hg -exclude Headers -exclude PrivateHeaders -exclude Modules -exclude \*.tbd -resolve-src-symlinks /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ClockerHelper.app /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Library/LoginItems
ProcessInfoPlistFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Info.plist /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/Clocker-Info.plist (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-infoPlistUtility /Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/Clocker-Info.plist -producttype com.apple.product-type.application -genpkginfo /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/PkgInfo -expandbuildsettings -platform macosx -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/AppFeedbackWindow-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Preferences-SBPartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/en.lproj/Panel-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/FloatingWindow-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/WelcomeView-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Onboarding-SBPartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/NotesPopover-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/MainMenu-PartialInfo.plist -additionalcontentfile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/assetcatalog_generated_info.plist -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Info.plist
GenerateDSYMFile /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app.dSYM /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/MacOS/Clocker (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/MacOS/Clocker -o /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app.dSYM
ValidateEmbeddedBinary /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Library/LoginItems/ClockerHelper.app (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-embeddedBinaryValidationUtility /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Library/LoginItems/ClockerHelper.app -signing-cert - -info-plist-path /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Info.plist
PBXCp /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/ShortcutRecorder.framework (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-copy -exclude .DS_Store -exclude CVS -exclude .svn -exclude .git -exclude .hg -exclude Headers -exclude PrivateHeaders -exclude Modules -exclude \*.tbd -resolve-src-symlinks /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/ShortcutRecorder.framework /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks
PBXCp /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/PTHotKey.framework (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
builtin-copy -exclude .DS_Store -exclude CVS -exclude .svn -exclude .git -exclude .hg -exclude Headers -exclude PrivateHeaders -exclude Modules -exclude \*.tbd -resolve-src-symlinks /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/PTHotKey.framework /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks
CodeSign /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/PTHotKey.framework/Versions/A (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
export CODESIGN_ALLOCATE=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
Signing Identity: "-"
/usr/bin/codesign --force --sign - --timestamp=none --preserve-metadata=identifier,entitlements,flags /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/PTHotKey.framework/Versions/A
CodeSign /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/ShortcutRecorder.framework/Versions/A (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
export CODESIGN_ALLOCATE=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
Signing Identity: "-"
/usr/bin/codesign --force --sign - --timestamp=none --preserve-metadata=identifier,entitlements,flags /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Frameworks/ShortcutRecorder.framework/Versions/A
PhaseScriptExecution Move\ .app\ to\ Applications /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Script-C2A632A020EAC5EE00EB6BEA.sh (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
export ACTION=build
export ALTERNATE_GROUP=1876110778
export ALTERNATE_MODE=u+w,go-w,a+rX
export ALTERNATE_OWNER=ban
export ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO
export ALWAYS_SEARCH_USER_PATHS=NO
export ALWAYS_USE_SEPARATE_HEADERMAPS=NO
export APPLE_INTERNAL_DEVELOPER_DIR=/AppleInternal/Developer
export APPLE_INTERNAL_DIR=/AppleInternal
export APPLE_INTERNAL_DOCUMENTATION_DIR=/AppleInternal/Documentation
export APPLE_INTERNAL_LIBRARY_DIR=/AppleInternal/Library
export APPLE_INTERNAL_TOOLS=/AppleInternal/Developer/Tools
export APPLICATION_EXTENSION_API_ONLY=NO
export APPLY_RULES_IN_COPY_FILES=NO
export ARCHS=x86_64
export ARCHS_STANDARD=x86_64
export ARCHS_STANDARD_32_64_BIT="x86_64 i386"
export ARCHS_STANDARD_32_BIT=i386
export ARCHS_STANDARD_64_BIT=x86_64
export ARCHS_STANDARD_INCLUDING_64_BIT=x86_64
export ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon
export AVAILABLE_PLATFORMS="appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"
export BITCODE_GENERATION_MODE=marker
export BUILD_ACTIVE_RESOURCES_ONLY=NO
export BUILD_COMPONENTS="headers build"
export BUILD_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products
export BUILD_ROOT=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products
export BUILD_STYLE=
export BUILD_VARIANTS=normal
export BUILT_PRODUCTS_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug
export CACHE_ROOT=/var/folders/r7/3v42n4q52fsft8jwnm5htq3s0wgch_/C/com.apple.DeveloperTools/10.2-10E125/Xcode
export CCHROOT=/var/folders/r7/3v42n4q52fsft8jwnm5htq3s0wgch_/C/com.apple.DeveloperTools/10.2-10E125/Xcode
export CHMOD=/bin/chmod
export CHOWN=/usr/sbin/chown
export CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED=YES
export CLANG_COVERAGE_MAPPING=YES
export CLANG_ENABLE_MODULES=YES
export CLANG_ENABLE_OBJC_ARC=YES
export CLANG_MODULES_BUILD_SESSION_FILE=/Users/ban/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation
export CLANG_PROFILE_DATA_DIRECTORY=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/ProfileData
export CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING=YES
export CLANG_WARN_BOOL_CONVERSION=YES
export CLANG_WARN_COMMA=YES
export CLANG_WARN_CONSTANT_CONVERSION=YES
export CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS=YES
export CLANG_WARN_EMPTY_BODY=YES
export CLANG_WARN_ENUM_CONVERSION=YES
export CLANG_WARN_INFINITE_RECURSION=YES
export CLANG_WARN_INT_CONVERSION=YES
export CLANG_WARN_NON_LITERAL_NULL_CONVERSION=YES
export CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF=YES
export CLANG_WARN_OBJC_LITERAL_CONVERSION=YES
export CLANG_WARN_RANGE_LOOP_ANALYSIS=YES
export CLANG_WARN_STRICT_PROTOTYPES=YES
export CLANG_WARN_SUSPICIOUS_MOVE=YES
export CLANG_WARN_UNREACHABLE_CODE=YES
export CLANG_WARN__DUPLICATE_METHOD_MATCH=YES
export CLASS_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/JavaClasses
export CLEAN_PRECOMPS=YES
export CLONE_HEADERS=NO
export CODESIGNING_FOLDER_PATH=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app
export CODE_SIGNING_ALLOWED=YES
export CODE_SIGN_ENTITLEMENTS=Clocker/Clocker.entitlements
export CODE_SIGN_IDENTITY=-
export CODE_SIGN_INJECT_BASE_ENTITLEMENTS=YES
export CODE_SIGN_STYLE=Manual
export COLOR_DIAGNOSTICS=NO
export COMBINE_HIDPI_IMAGES=YES
export COMPILER_INDEX_STORE_ENABLE=Default
export COMPOSITE_SDK_DIRS=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/CompositeSDKs
export COMPRESS_PNG_FILES=NO
export CONFIGURATION=Debug
export CONFIGURATION_BUILD_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug
export CONFIGURATION_TEMP_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug
export CONTENTS_FOLDER_PATH=Clocker.app/Contents
export COPYING_PRESERVES_HFS_DATA=NO
export COPY_HEADERS_RUN_UNIFDEF=NO
export COPY_PHASE_STRIP=NO
export COPY_RESOURCES_FROM_STATIC_FRAMEWORKS=YES
export CP=/bin/cp
export CREATE_INFOPLIST_SECTION_IN_BINARY=NO
export CURRENT_ARCH=undefined_arch
export CURRENT_VARIANT=normal
export DEAD_CODE_STRIPPING=NO
export DEBUGGING_SYMBOLS=YES
export DEBUG_INFORMATION_FORMAT=dwarf-with-dsym
export DEFAULT_COMPILER=com.apple.compilers.llvm.clang.1_0
export DEFAULT_KEXT_INSTALL_PATH=/System/Library/Extensions
export DEFINES_MODULE=YES
export DEPLOYMENT_LOCATION=NO
export DEPLOYMENT_POSTPROCESSING=NO
export DEPLOYMENT_TARGET_CLANG_ENV_NAME=MACOSX_DEPLOYMENT_TARGET
export DEPLOYMENT_TARGET_CLANG_FLAG_NAME=mmacosx-version-min
export DEPLOYMENT_TARGET_LD_ENV_NAME=MACOSX_DEPLOYMENT_TARGET
export DEPLOYMENT_TARGET_LD_FLAG_NAME=macosx_version_min
export DEPLOYMENT_TARGET_SETTING_NAME=MACOSX_DEPLOYMENT_TARGET
export DEPLOYMENT_TARGET_SUGGESTED_VALUES="10.6 10.7 10.8 10.9 10.10 10.11 10.12 10.13 10.14"
export DERIVED_FILES_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/DerivedSources
export DERIVED_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/DerivedSources
export DERIVED_SOURCES_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/DerivedSources
export DEVELOPER_APPLICATIONS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications
export DEVELOPER_BIN_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr/bin
export DEVELOPER_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer
export DEVELOPER_FRAMEWORKS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Library/Frameworks
export DEVELOPER_FRAMEWORKS_DIR_QUOTED=/Applications/Xcode_10.2_fb.app/Contents/Developer/Library/Frameworks
export DEVELOPER_LIBRARY_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Library
export DEVELOPER_SDK_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
export DEVELOPER_TOOLS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Tools
export DEVELOPER_USR_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr
export DEVELOPMENT_LANGUAGE=English
export DOCUMENTATION_FOLDER_PATH=Clocker.app/Contents/Resources/English.lproj/Documentation
export DO_HEADER_SCANNING_IN_JAM=NO
export DSTROOT=/tmp/Clocker.dst
export DT_TOOLCHAIN_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain
export DWARF_DSYM_FILE_NAME=Clocker.app.dSYM
export DWARF_DSYM_FILE_SHOULD_ACCOMPANY_PRODUCT=NO
export DWARF_DSYM_FOLDER_PATH=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug
export EMBEDDED_CONTENT_CONTAINS_SWIFT=NO
export EMBEDDED_PROFILE_NAME=embedded.provisionprofile
export EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE=NO
export ENABLE_BITCODE=NO
export ENABLE_DEFAULT_HEADER_SEARCH_PATHS=YES
export ENABLE_HEADER_DEPENDENCIES=YES
export ENABLE_ON_DEMAND_RESOURCES=NO
export ENABLE_STRICT_OBJC_MSGSEND=YES
export ENABLE_TESTABILITY=YES
export EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS=".DS_Store .svn .git .hg CVS"
export EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES="*.nib *.lproj *.framework *.gch *.xcode* *.xcassets (*) .DS_Store CVS .svn .git .hg *.pbproj *.pbxproj"
export EXECUTABLES_FOLDER_PATH=Clocker.app/Contents/Executables
export EXECUTABLE_FOLDER_PATH=Clocker.app/Contents/MacOS
export EXECUTABLE_NAME=Clocker
export EXECUTABLE_PATH=Clocker.app/Contents/MacOS/Clocker
export EXPANDED_CODE_SIGN_IDENTITY=-
export EXPANDED_CODE_SIGN_IDENTITY_NAME=-
export FILE_LIST=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects/LinkFileList
export FIXED_FILES_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/FixedFiles
export FRAMEWORKS_FOLDER_PATH=Clocker.app/Contents/Frameworks
export FRAMEWORK_FLAG_PREFIX=-framework
export FRAMEWORK_SEARCH_PATHS="/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug /Users/ban/Documents/GitHub/Clocker/Clocker /Users/ban/Documents/GitHub/Clocker/Clocker/Frameworks"
export FRAMEWORK_VERSION=A
export FULL_PRODUCT_NAME=Clocker.app
export GCC3_VERSION=3.3
export GCC_C_LANGUAGE_STANDARD=gnu99
export GCC_DYNAMIC_NO_PIC=NO
export GCC_ENABLE_OBJC_EXCEPTIONS=YES
export GCC_INLINES_ARE_PRIVATE_EXTERN=YES
export GCC_NO_COMMON_BLOCKS=YES
export GCC_OPTIMIZATION_LEVEL=0
export GCC_PFE_FILE_C_DIALECTS="c objective-c c++ objective-c++"
export GCC_PRECOMPILE_PREFIX_HEADER=YES
export GCC_PREFIX_HEADER=Clocker/Clocker-Prefix.pch
export GCC_PREPROCESSOR_DEFINITIONS=DEBUG
export GCC_SYMBOLS_PRIVATE_EXTERN=NO
export GCC_TREAT_WARNINGS_AS_ERRORS=NO
export GCC_VERSION=com.apple.compilers.llvm.clang.1_0
export GCC_VERSION_IDENTIFIER=com_apple_compilers_llvm_clang_1_0
export GCC_WARN_64_TO_32_BIT_CONVERSION=YES
export GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS=YES
export GCC_WARN_ABOUT_MISSING_PROTOTYPES=YES
export GCC_WARN_ABOUT_RETURN_TYPE=YES
export GCC_WARN_UNDECLARED_SELECTOR=YES
export GCC_WARN_UNINITIALIZED_AUTOS=YES
export GCC_WARN_UNUSED_FUNCTION=YES
export GCC_WARN_UNUSED_LABEL=YES
export GCC_WARN_UNUSED_PARAMETER=YES
export GCC_WARN_UNUSED_VARIABLE=YES
export GENERATE_MASTER_OBJECT_FILE=NO
export GENERATE_PKGINFO_FILE=YES
export GENERATE_PROFILING_CODE=NO
export GENERATE_TEXT_BASED_STUBS=NO
export GID=1876110778
export GROUP=1876110778
export HEADERMAP_INCLUDES_FLAT_ENTRIES_FOR_TARGET_BEING_BUILT=YES
export HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_ALL_PRODUCT_TYPES=YES
export HEADERMAP_INCLUDES_NONPUBLIC_NONPRIVATE_HEADERS=YES
export HEADERMAP_INCLUDES_PROJECT_HEADERS=YES
export HEADERMAP_USES_FRAMEWORK_PREFIX_ENTRIES=YES
export HEADERMAP_USES_VFS=YES
export HEADER_SEARCH_PATHS="/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/include "
export HIDE_BITCODE_SYMBOLS=YES
export HOME=/Users/ban
export ICONV=/usr/bin/iconv
export INFOPLIST_EXPAND_BUILD_SETTINGS=YES
export INFOPLIST_FILE=Clocker/Clocker-Info.plist
export INFOPLIST_OUTPUT_FORMAT=same-as-input
export INFOPLIST_PATH=Clocker.app/Contents/Info.plist
export INFOPLIST_PREPROCESS=NO
export INFOSTRINGS_PATH=Clocker.app/Contents/Resources/English.lproj/InfoPlist.strings
export INLINE_PRIVATE_FRAMEWORKS=NO
export INSTALLHDRS_COPY_PHASE=NO
export INSTALLHDRS_SCRIPT_PHASE=NO
export INSTALL_DIR=/tmp/Clocker.dst/Applications
export INSTALL_GROUP=1876110778
export INSTALL_MODE_FLAG=u+w,go-w,a+rX
export INSTALL_OWNER=ban
export INSTALL_PATH=/Applications
export INSTALL_ROOT=/tmp/Clocker.dst
export JAVAC_DEFAULT_FLAGS="-J-Xms64m -J-XX:NewSize=4M -J-Dfile.encoding=UTF8"
export JAVA_APP_STUB=/System/Library/Frameworks/JavaVM.framework/Resources/MacOS/JavaApplicationStub
export JAVA_ARCHIVE_CLASSES=YES
export JAVA_ARCHIVE_TYPE=JAR
export JAVA_COMPILER=/usr/bin/javac
export JAVA_FOLDER_PATH=Clocker.app/Contents/Resources/Java
export JAVA_FRAMEWORK_RESOURCES_DIRS=Resources
export JAVA_JAR_FLAGS=cv
export JAVA_SOURCE_SUBDIR=.
export JAVA_USE_DEPENDENCIES=YES
export JAVA_ZIP_FLAGS=-urg
export JIKES_DEFAULT_FLAGS="+E +OLDCSO"
export KASAN_DEFAULT_CFLAGS="-DKASAN=1 -fsanitize=address -mllvm -asan-globals-live-support -mllvm -asan-force-dynamic-shadow"
export KEEP_PRIVATE_EXTERNS=NO
export LD_DEPENDENCY_INFO_FILE=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects-normal/undefined_arch/Clocker_dependency_info.dat
export LD_GENERATE_MAP_FILE=NO
export LD_MAP_FILE_PATH=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Clocker-LinkMap-normal-undefined_arch.txt
export LD_NO_PIE=NO
export LD_QUOTE_LINKER_ARGUMENTS_FOR_COMPILER_DRIVER=YES
export LD_RUNPATH_SEARCH_PATHS=" @executable_path/../Frameworks"
export LEGACY_DEVELOPER_DIR=/Applications/Xcode_10.2_fb.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer
export LEX=lex
export LIBRARY_FLAG_NOSPACE=YES
export LIBRARY_FLAG_PREFIX=-l
export LIBRARY_KEXT_INSTALL_PATH=/Library/Extensions
export LIBRARY_SEARCH_PATHS="/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug "
export LINKER_DISPLAYS_MANGLED_NAMES=NO
export LINK_FILE_LIST_normal_x86_64=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects-normal/x86_64/Clocker.LinkFileList
export LINK_WITH_STANDARD_LIBRARIES=YES
export LOCALIZED_RESOURCES_FOLDER_PATH=Clocker.app/Contents/Resources/English.lproj
export LOCALIZED_STRING_MACRO_NAMES="NSLocalizedString CFLocalizedString"
export LOCAL_ADMIN_APPS_DIR=/Applications/Utilities
export LOCAL_APPS_DIR=/Applications
export LOCAL_DEVELOPER_DIR=/Library/Developer
export LOCAL_LIBRARY_DIR=/Library
export LOCROOT=/Users/ban/Documents/GitHub/Clocker/Clocker
export LOCSYMROOT=/Users/ban/Documents/GitHub/Clocker/Clocker
export MACH_O_TYPE=mh_execute
export MACOSX_DEPLOYMENT_TARGET=10.12
export MAC_OS_X_PRODUCT_BUILD_VERSION=18E226
export MAC_OS_X_VERSION_ACTUAL=101404
export MAC_OS_X_VERSION_MAJOR=101400
export MAC_OS_X_VERSION_MINOR=1404
export METAL_LIBRARY_FILE_BASE=default
export METAL_LIBRARY_OUTPUT_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app/Contents/Resources
export MODULE_CACHE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/ModuleCache.noindex
export NATIVE_ARCH=i386
export NATIVE_ARCH_32_BIT=i386
export NATIVE_ARCH_64_BIT=x86_64
export NATIVE_ARCH_ACTUAL=x86_64
export NO_COMMON=YES
export OBJECT_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects
export OBJECT_FILE_DIR_normal=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects-normal
export OBJROOT=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex
export ONLY_ACTIVE_ARCH=NO
export OS=MACOS
export OSAC=/usr/bin/osacompile
export OTHER_SWIFT_FLAGS="-D DEBUG"
export PACKAGE_TYPE=com.apple.package-type.wrapper.application
export PASCAL_STRINGS=YES
export PATH=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/libexec:/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/usr/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/usr/local/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/local/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/usr/bin:/Applications/Xcode_10.2_fb.app/Contents/Developer/usr/local/bin:/opt/facebook/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/facebook/bin/biggrep:/opt/facebook/nuclide/latest/nuclide/pkg/fb-on-demand-cli/bin:/opt/facebook/ops/scripts/chef:/opt/homebrew/bin:/usr/local/munki:/opt/facebook/hg/bin
export PATH_PREFIXES_EXCLUDED_FROM_HEADER_DEPENDENCIES="/usr/include /usr/local/include /System/Library/Frameworks /System/Library/PrivateFrameworks /Applications/Xcode_10.2_fb.app/Contents/Developer/Headers /Applications/Xcode_10.2_fb.app/Contents/Developer/SDKs /Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms"
export PBDEVELOPMENTPLIST_PATH=Clocker.app/Contents/pbdevelopment.plist
export PER_ARCH_OBJECT_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects-normal/undefined_arch
export PER_VARIANT_OBJECT_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Objects-normal
export PKGINFO_FILE_PATH=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/PkgInfo
export PKGINFO_PATH=Clocker.app/Contents/PkgInfo
export PLATFORM_DEVELOPER_APPLICATIONS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications
export PLATFORM_DEVELOPER_BIN_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr/bin
export PLATFORM_DEVELOPER_LIBRARY_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Library
export PLATFORM_DEVELOPER_SDK_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
export PLATFORM_DEVELOPER_TOOLS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Tools
export PLATFORM_DEVELOPER_USR_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr
export PLATFORM_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform
export PLATFORM_DISPLAY_NAME=macOS
export PLATFORM_NAME=macosx
export PLATFORM_PREFERRED_ARCH=x86_64
export PLATFORM_PRODUCT_BUILD_VERSION=10E125
export PLIST_FILE_OUTPUT_FORMAT=same-as-input
export PLUGINS_FOLDER_PATH=Clocker.app/Contents/PlugIns
export PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR=YES
export PRECOMP_DESTINATION_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/PrefixHeaders
export PRESERVE_DEAD_CODE_INITS_AND_TERMS=NO
export PRIVATE_HEADERS_FOLDER_PATH=Clocker.app/Contents/PrivateHeaders
export PRODUCT_BUNDLE_IDENTIFIER=com.abhishek.Clocker
export PRODUCT_MODULE_NAME=Clocker
export PRODUCT_NAME=Clocker
export PRODUCT_SETTINGS_PATH=/Users/ban/Documents/GitHub/Clocker/Clocker/Clocker/Clocker-Info.plist
export PRODUCT_TYPE=com.apple.product-type.application
export PROFILING_CODE=NO
export PROJECT=Clocker
export PROJECT_DERIVED_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/DerivedSources
export PROJECT_DIR=/Users/ban/Documents/GitHub/Clocker/Clocker
export PROJECT_FILE_PATH=/Users/ban/Documents/GitHub/Clocker/Clocker/Clocker.xcodeproj
export PROJECT_NAME=Clocker
export PROJECT_TEMP_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build
export PROJECT_TEMP_ROOT=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex
export PUBLIC_HEADERS_FOLDER_PATH=Clocker.app/Contents/Headers
export RECURSIVE_SEARCH_PATHS_FOLLOW_SYMLINKS=YES
export REMOVE_CVS_FROM_RESOURCES=YES
export REMOVE_GIT_FROM_RESOURCES=YES
export REMOVE_HEADERS_FROM_EMBEDDED_BUNDLES=YES
export REMOVE_HG_FROM_RESOURCES=YES
export REMOVE_SVN_FROM_RESOURCES=YES
export REZ_COLLECTOR_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/ResourceManagerResources
export REZ_OBJECTS_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/ResourceManagerResources/Objects
export REZ_SEARCH_PATHS="/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug "
export SCAN_ALL_SOURCE_FILES_FOR_INCLUDES=NO
export SCRIPTS_FOLDER_PATH=Clocker.app/Contents/Resources/Scripts
export SCRIPT_INPUT_FILE_COUNT=0
export SCRIPT_INPUT_FILE_LIST_COUNT=0
export SCRIPT_OUTPUT_FILE_COUNT=0
export SCRIPT_OUTPUT_FILE_LIST_COUNT=0
export SDKROOT=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk
export SDK_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk
export SDK_DIR_macosx10_14=/Applications/Xcode_10.2_fb.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk
export SDK_NAME=macosx10.14
export SDK_NAMES=macosx10.14
export SDK_PRODUCT_BUILD_VERSION=18E219
export SDK_VERSION=10.14
export SDK_VERSION_ACTUAL=101400
export SDK_VERSION_MAJOR=101400
export SDK_VERSION_MINOR=1400
export SED=/usr/bin/sed
export SEPARATE_STRIP=NO
export SEPARATE_SYMBOL_EDIT=NO
export SET_DIR_MODE_OWNER_GROUP=YES
export SET_FILE_MODE_OWNER_GROUP=NO
export SHALLOW_BUNDLE=NO
export SHARED_DERIVED_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/DerivedSources
export SHARED_FRAMEWORKS_FOLDER_PATH=Clocker.app/Contents/SharedFrameworks
export SHARED_PRECOMPS_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/PrecompiledHeaders
export SHARED_SUPPORT_FOLDER_PATH=Clocker.app/Contents/SharedSupport
export SKIP_INSTALL=NO
export SOURCE_ROOT=/Users/ban/Documents/GitHub/Clocker/Clocker
export SRCROOT=/Users/ban/Documents/GitHub/Clocker/Clocker
export STRINGS_FILE_OUTPUT_ENCODING=UTF-16
export STRIP_BITCODE_FROM_COPIED_FILES=NO
export STRIP_INSTALLED_PRODUCT=YES
export STRIP_PNG_TEXT=NO
export STRIP_STYLE=all
export STRIP_SWIFT_SYMBOLS=YES
export SUPPORTED_PLATFORMS=macosx
export SUPPORTS_TEXT_BASED_API=NO
export SWIFT_OBJC_BRIDGING_HEADER=Clocker-Bridging-Header.h
export SWIFT_OPTIMIZATION_LEVEL=-Onone
export SWIFT_PLATFORM_TARGET_PREFIX=macosx
export SWIFT_VERSION=5.0
export SYMROOT=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products
export SYSTEM_ADMIN_APPS_DIR=/Applications/Utilities
export SYSTEM_APPS_DIR=/Applications
export SYSTEM_CORE_SERVICES_DIR=/System/Library/CoreServices
export SYSTEM_DEMOS_DIR=/Applications/Extras
export SYSTEM_DEVELOPER_APPS_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications
export SYSTEM_DEVELOPER_BIN_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr/bin
export SYSTEM_DEVELOPER_DEMOS_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications/Utilities/Built Examples"
export SYSTEM_DEVELOPER_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer
export SYSTEM_DEVELOPER_DOC_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/ADC Reference Library"
export SYSTEM_DEVELOPER_GRAPHICS_TOOLS_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications/Graphics Tools"
export SYSTEM_DEVELOPER_JAVA_TOOLS_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications/Java Tools"
export SYSTEM_DEVELOPER_PERFORMANCE_TOOLS_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications/Performance Tools"
export SYSTEM_DEVELOPER_RELEASENOTES_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/ADC Reference Library/releasenotes"
export SYSTEM_DEVELOPER_TOOLS=/Applications/Xcode_10.2_fb.app/Contents/Developer/Tools
export SYSTEM_DEVELOPER_TOOLS_DOC_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/ADC Reference Library/documentation/DeveloperTools"
export SYSTEM_DEVELOPER_TOOLS_RELEASENOTES_DIR="/Applications/Xcode_10.2_fb.app/Contents/Developer/ADC Reference Library/releasenotes/DeveloperTools"
export SYSTEM_DEVELOPER_USR_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/usr
export SYSTEM_DEVELOPER_UTILITIES_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Applications/Utilities
export SYSTEM_DOCUMENTATION_DIR=/Library/Documentation
export SYSTEM_KEXT_INSTALL_PATH=/System/Library/Extensions
export SYSTEM_LIBRARY_DIR=/System/Library
export TAPI_VERIFY_MODE=ErrorsOnly
export TARGETNAME=Clocker
export TARGET_BUILD_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug
export TARGET_NAME=Clocker
export TARGET_TEMP_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build
export TEMP_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build
export TEMP_FILES_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build
export TEMP_FILE_DIR=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build
export TEMP_ROOT=/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex
export TEST_HOST=Clocker
export TOOLCHAINS=com.apple.dt.toolchain.XcodeDefault
export TOOLCHAIN_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain
export TREAT_MISSING_BASELINES_AS_TEST_FAILURES=NO
export UID=1103606282
export UNLOCALIZED_RESOURCES_FOLDER_PATH=Clocker.app/Contents/Resources
export UNSTRIPPED_PRODUCT=NO
export USER=ban
export USER_APPS_DIR=/Users/ban/Applications
export USER_LIBRARY_DIR=/Users/ban/Library
export USE_DYNAMIC_NO_PIC=YES
export USE_HEADERMAP=YES
export USE_HEADER_SYMLINKS=NO
export VALIDATE_PRODUCT=NO
export VALID_ARCHS="i386 x86_64"
export VERBOSE_PBXCP=NO
export VERSIONPLIST_PATH=Clocker.app/Contents/version.plist
export VERSION_INFO_BUILDER=ban
export VERSION_INFO_FILE=Clocker_vers.c
export VERSION_INFO_STRING=""@(#)PROGRAM:Clocker PROJECT:Clocker-""
export WRAPPER_EXTENSION=app
export WRAPPER_NAME=Clocker.app
export WRAPPER_SUFFIX=.app
export WRAP_ASSET_PACKS_IN_SEPARATE_DIRECTORIES=NO
export XCODE_APP_SUPPORT_DIR=/Applications/Xcode_10.2_fb.app/Contents/Developer/Library/Xcode
export XCODE_PRODUCT_BUILD_VERSION=10E125
export XCODE_VERSION_ACTUAL=1020
export XCODE_VERSION_MAJOR=1000
export XCODE_VERSION_MINOR=1020
export XPCSERVICES_FOLDER_PATH=Clocker.app/Contents/XPCServices
export YACC=yacc
export arch=undefined_arch
export variant=normal
/bin/sh -c /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Script-C2A632A020EAC5EE00EB6BEA.sh
CodeSign /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app (in target: Clocker)
cd /Users/ban/Documents/GitHub/Clocker/Clocker
export CODESIGN_ALLOCATE=/Applications/Xcode_10.2_fb.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
Signing Identity: "-"
/usr/bin/codesign --force --sign - --entitlements /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Intermediates.noindex/Clocker.build/Debug/Clocker.build/Clocker.app.xcent --timestamp=none /Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app
/Users/ban/Library/Developer/Xcode/DerivedData/Clocker-hedsfmwngzzibwbekcsaudypqlfo/Build/Products/Debug/Clocker.app: replacing existing signature
** BUILD SUCCEEDED **
Loading…
Cancel
Save