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

4
Clocker/Clocker/LocationController.swift

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

8
Clocker/ClockerUITests/AboutUsTests.swift

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

2
Clocker/ClockerUITests/FloatingWindowTests.swift

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

33
Clocker/ClockerUITests/NetworkDisconnectionTests.swift

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

86
Clocker/ClockerUITests/OnboardingTests.swift

@ -5,7 +5,7 @@ import XCTest
let CLOnboaringTestsLaunchArgument = "isTestingTheOnboardingFlow"
class OnboardingTests: XCTestCase {
var app: XCUIApplication!
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)
// 2. Static texts and button title's are appropriate
func testForwardButton() {
welcomeControllerTests()
// Let's go to the Permissions View
moveForward()
permissionsControllerTests()
// Time to test the launchAtLoginView
moveForward()
startupControllerTests()
// Let's go to OnboardingSearchController
moveForward()
searchControllerTests()
// Let's go to the FinalOnboardingController
moveForward()
finalOnboardingControllerTests()
backButtonTests()
}
func backButtonTests() {
moveBackward()
searchControllerTests()
moveBackward()
startupControllerTests()
moveBackward()
permissionsControllerTests()
moveBackward()
welcomeControllerTests()
alternateStartupFlowTests()
}
func alternateStartupFlowTests() {
// Let's go to the Permissions View
moveForward()
permissionsControllerTests()
// Time to test the launchAtLoginView
moveForward()
startupControllerTests()
// Let's go to OnboardingSearchController
alternateMoveForward()
searchControllerTests()
// Let's go to the FinalOnboardingController
moveForward()
finalOnboardingControllerTests()
moveForward()
XCTAssertTrue(app.statusItems.count > 0, "Status item was not installed in the menubar")
}
private func moveForward() {
let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Forward"].click()
sleep(1)
}
private func alternateMoveForward() {
let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Alternate"].click()
sleep(1)
}
private func moveBackward() {
let onboardingWindow = app.windows["OnboardingWindow"]
onboardingWindow.buttons["Backward"].click()
sleep(1)
}
private func welcomeControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
// Tests static texts
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.")
let button = onboardingWindow.buttons["Forward"]
// Test the button 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!")
}
private func permissionsControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
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.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong")
XCTAssertTrue(onboardingWindow.buttons["Backward"].exists, "Back button was unexpectedly missing")
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
}
private func startupControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
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.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.")
}
private func searchControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
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.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.")
}
private func finalOnboardingControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
// Let's test the buttons
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.")
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
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)
}
func testRightMouseDownToShowPopover() {
app.tapMenubarIcon()
let cell = app.tables["mainTableView"].cells.firstMatch
cell.rightClick()
XCTAssert(app.popovers.count > 0)
}
}

82
Clocker/ClockerUITests/PreferencesTest.swift

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

14
Clocker/ClockerUITests/ReviewTests.swift

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

28
Clocker/ClockerUITests/ShortcutTests.swift

@ -3,47 +3,47 @@
import XCTest
class ShortcutTests: XCTestCase {
var app: XCUIApplication!
let randomIndex = Int(arc4random_uniform(26))
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
app.tapMenubarIcon()
if !app.tables["mainTableView"].exists {
app.buttons["FloatingPin"].click()
app.tapMenubarIcon()
}
}
func testShortcuts() {
app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
XCTAssertFalse(app.tables["mainTableView"].exists)
let randomAlphabet = randomLetter()
app.windows["Clocker"].buttons["ShortcutControl"].click()
app.windows["Clocker"].buttons["ShortcutControl"].typeKey(randomAlphabet, modifierFlags: [.shift, .command])
// Close the window to really test
app.windows["Clocker"].buttons["_XCUI:CloseWindow"].click()
app.typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertTrue(app.tables["mainTableView"].exists)
app.terminate()
app.launch()
app.typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertTrue(app.tables["mainTableView"].exists)
// Reset the shortcut
app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
app.windows["Clocker"].buttons["ShortcutControl"].click()
@ -51,7 +51,7 @@ class ShortcutTests: XCTestCase {
app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertFalse(app.tables["mainTableView"].exists)
}
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"]
return alphabet[randomIndex]

104
Clocker/ClockerUnitTests/ClockerUnitTests.swift

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

2
Clocker/ClockerUnitTests/RateTests.swift

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

3
Clocker/Dependencies/Date Additions/Constants.swift

@ -6,7 +6,6 @@
// Copyright © 2016 Grayson Webster. All rights reserved.
//
import Foundation
/**
@ -24,6 +23,6 @@ public class Constants {
public static let SecondsInHour: TimeInterval = 3600
public static let SecondsInMinute: TimeInterval = 60
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]
}

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

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

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

@ -27,7 +27,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent
return calendar.component(component, from: self)
}
/**
* Convenient accessor of the date's `Calendar` components ordinality.
*
@ -41,7 +41,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent
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.
*
@ -60,10 +60,10 @@ public extension Date {
var unitRange: Range<Int>?
if larger.hashValue < smaller.hashValue {
for x in larger.hashValue..<smaller.hashValue {
var stepLarger: Calendar.Component
var stepSmaller: Calendar.Component
switch(x) {
case 0:
stepLarger = Calendar.Component.era
@ -110,7 +110,7 @@ public extension Date {
default:
return nil
}
if unitRange?.count != nil {
units *= (unitRange?.count)!
}
@ -119,107 +119,107 @@ public extension Date {
}
return nil
}
// MARK: - Components
/**
* Convenience getter for the date's `era` component
*/
var era: Int {
return component(.era)
}
/**
* Convenience getter for the date's `year` component
*/
var year: Int {
return component(.year)
}
/**
* Convenience getter for the date's `month` component
*/
var month: Int {
return component(.month)
}
/**
* Convenience getter for the date's `week` component
*/
var week: Int {
return component(.weekday)
}
/**
* Convenience getter for the date's `day` component
*/
var day: Int {
return component(.day)
}
/**
* Convenience getter for the date's `hour` component
*/
var hour: Int {
return component(.hour)
}
/**
* Convenience getter for the date's `minute` component
*/
var minute: Int {
return component(.minute)
}
/**
* Convenience getter for the date's `second` component
*/
var second: Int {
return component(.second)
}
/**
* Convenience getter for the date's `weekday` component
*/
var weekday: Int {
return component(.weekday)
}
/**
* Convenience getter for the date's `weekdayOrdinal` component
*/
var weekdayOrdinal: Int {
return component(.weekdayOrdinal)
}
/**
* Convenience getter for the date's `quarter` component
*/
var quarter: Int {
return component(.quarter)
}
/**
* Convenience getter for the date's `weekOfYear` component
*/
var weekOfMonth: Int {
return component(.weekOfMonth)
}
/**
* Convenience getter for the date's `weekOfYear` component
*/
var weekOfYear: Int {
return component(.weekOfYear)
}
/**
* Convenience getter for the date's `yearForWeekOfYear` component
*/
var yearForWeekOfYear: Int {
return component(.yearForWeekOfYear)
}
/**
* 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)
return days!.count
}
// MARK: - Set Components
/**
* Convenience setter for the date's `year` component
*/
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)
}
/**
* Convenience setter for the date's `month` component
*/
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)
}
/**
* Convenience setter for the date's `day` component
*/
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)
}
/**
* Convenience setter for the date's `hour` component
*/
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)
}
/**
* Convenience setter for the date's `minute` component
*/
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)
}
/**
* Convenience setter for the date's `second` component
*/
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)
}
// MARK: - Bools
/**
* Determine if date is in a leap year
*/
var isInLeapYear: Bool {
let yearComponent = component(.year)
if yearComponent % 400 == 0 {
return true
}
@ -293,7 +292,7 @@ public extension Date {
}
return false
}
/**
* Determine if date is within the current day
*/
@ -301,7 +300,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInToday(self)
}
/**
* Determine if date is within the day tomorrow
*/
@ -309,7 +308,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInTomorrow(self)
}
/**
* Determine if date is within yesterday
*/
@ -317,7 +316,7 @@ public extension Date {
let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInYesterday(self)
}
/**
* 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.
*/
public extension Date {
// MARK: - Formatted Date - Style
/**
* Get string representation of date.
*
@ -29,10 +29,10 @@ public extension Date {
dateFormatter.dateStyle = dateStyle
dateFormatter.timeZone = timeZone
dateFormatter.locale = locale
return dateFormatter.string(from: self)
}
/**
* 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)
#endif
}
/**
* Get string representation of date.
* 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 {
return format(with: dateStyle, timeZone: TimeZone.autoupdatingCurrent, locale: locale)
}
/**
* Get string representation of date.
* 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)
#endif
}
// MARK: - Formatted Date - String
/**
* Get string representation of date.
*
@ -95,10 +94,10 @@ public extension Date {
dateFormatter.dateFormat = dateFormat
dateFormatter.timeZone = timeZone
dateFormatter.locale = locale
return dateFormatter.string(from: self)
}
/**
* Get string representation of date.
* 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)
#endif
}
/**
* Get string representation of date.
* 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 {
return format(with: dateFormat, timeZone: TimeZone.autoupdatingCurrent, locale: locale)
}
/**
* Get string representation of date.
* 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 {
// MARK: - Initializers
/**
* Init date with components.
*
@ -35,14 +35,14 @@ public extension Date {
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
guard let date = Calendar.current.date(from: dateComponents) else {
self = Date()
return
}
self = date
}
/**
* 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) {
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.
*
@ -63,18 +63,18 @@ public extension Date {
*/
init(dateString: String, format: String, timeZone: TimeZone) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none;
dateFormatter.timeStyle = .none;
dateFormatter.timeZone = timeZone;
dateFormatter.dateFormat = format;
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none
dateFormatter.timeZone = timeZone
dateFormatter.dateFormat = format
guard let date = dateFormatter.date(from: dateString) else {
self = Date()
return
}
self = date
}
/**
* Init date from string, given a format string, according to Apple's date formatting guide.
* Time Zone automatically selected as the current time zone.
@ -84,5 +84,5 @@ public extension Date {
*/
init (dateString: String, format: String) {
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
*/
public extension Date {
// MARK: - StartOf
/**
* 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
*/
func start(of component: Component) -> Date {
var newDate = self;
var newDate = self
if component == .second {
newDate.second(self.second)
}
else if component == .minute {
} else if component == .minute {
newDate.second(0)
} else if component == .hour {
newDate.second(0)
@ -51,7 +50,7 @@ public extension Date {
}
return newDate
}
/**
* 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
*/
func end(of component: Component) -> Date {
var newDate = self;
var newDate = self
if component == .second {
newDate.second(newDate.second + 1)
newDate = newDate - 0.001
}
else if component == .minute {
} else if component == .minute {
newDate.second(60)
newDate = newDate - 0.001
} else if component == .hour {
@ -92,10 +90,10 @@ public extension Date {
newDate.month(12)
newDate.day(31)
}
return newDate
}
func daysInMonth(date: Date) -> Int {
let month = date.month
if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 {
@ -112,10 +110,9 @@ public extension Date {
return 30
}
}
// MARK: - Addition / Subtractions
/**
* # Add (TimeChunk to Date)
* Increase a date by the value of a given `TimeChunk`.
@ -136,7 +133,7 @@ public extension Date {
components.second = chunk.seconds
return calendar.date(byAdding: components, to: self)!
}
/**
* # Subtract (TimeChunk from Date)
* Decrease a date by the value of a given `TimeChunk`.
@ -157,36 +154,35 @@ public extension Date {
components.second = -chunk.seconds
return calendar.date(byAdding: components, to: self)!
}
// MARK: - Operator Overloads
/**
* Operator overload for adding a `TimeChunk` to a date.
*/
static func +(leftAddend: Date, rightAddend: TimeChunk) -> Date {
return leftAddend.add(rightAddend)
}
/**
* Operator overload for subtracting a `TimeChunk` from a date.
*/
static func -(minuend: Date, subtrahend: TimeChunk) -> Date {
return minuend.subtract(subtrahend)
}
/**
* Operator overload for adding a `TimeInterval` to a date.
*/
static func +(leftAddend: Date, rightAddend: Int) -> Date {
return leftAddend.addingTimeInterval((TimeInterval(rightAddend)))
}
/**
* Operator overload for subtracting a `TimeInterval` from a date.
*/
static func -(minuend: Date, subtrahend: Int) -> Date {
return minuend.addingTimeInterval(-(TimeInterval(subtrahend)))
}
}

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

@ -13,9 +13,9 @@ import Foundation
* time in String format.
*/
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
* how far in the past that date is from now.
@ -24,10 +24,10 @@ public extension Date {
*
* - 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)
}
/**
* 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.
@ -39,7 +39,7 @@ public extension Date {
static func shortTimeAgo(since date:Date) -> String {
return date.shortTimeAgo(since:Date())
}
/**
* Returns a string with the most convenient unit of time representing
* how far in the past that date is from now.
@ -49,7 +49,7 @@ public extension Date {
var timeAgoSinceNow: String {
return self.timeAgo(since:Date())
}
/**
* Returns a shortened string with the most convenient unit of time representing
* how far in the past that date is from now.
@ -59,184 +59,156 @@ public extension Date {
var shortTimeAgoSinceNow: String {
return self.shortTimeAgo(since:Date())
}
func timeAgo(since date:Date, numericDates: Bool = false, numericTimes: Bool = false) -> String {
let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year])
let earliest = self.earlierDate(date)
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 yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day
//Not Yet Implemented/Optional
//The following strings are present in the translation files but lack logic as of 2014.04.05
//@"Today", @"This week", @"This month", @"This year"
//and @"This morning", @"This afternoon"
if (components.year! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!)
}
else if (components.year! >= 1) {
} else if (components.year! >= 1) {
if (numericDates) {
return DateToolsLocalizedStrings("1 year ago");
return DateToolsLocalizedStrings("1 year ago")
}
return DateToolsLocalizedStrings("Last year");
}
else if (components.month! >= 2) {
return DateToolsLocalizedStrings("Last year")
} else if (components.month! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!)
}
else if (components.month! >= 1) {
} else if (components.month! >= 1) {
if (numericDates) {
return DateToolsLocalizedStrings("1 month ago");
return DateToolsLocalizedStrings("1 month ago")
}
return DateToolsLocalizedStrings("Last month");
}
else if (components.weekOfYear! >= 2) {
return DateToolsLocalizedStrings("Last month")
} else if (components.weekOfYear! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!)
}
else if (components.weekOfYear! >= 1) {
} else if (components.weekOfYear! >= 1) {
if (numericDates) {
return DateToolsLocalizedStrings("1 week ago");
return DateToolsLocalizedStrings("1 week ago")
}
return DateToolsLocalizedStrings("Last week");
}
else if (components.day! >= 2) {
return DateToolsLocalizedStrings("Last week")
} else if (components.day! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!)
}
else if (isYesterday) {
} else if (isYesterday) {
if (numericDates) {
return DateToolsLocalizedStrings("1 day ago");
return DateToolsLocalizedStrings("1 day ago")
}
return DateToolsLocalizedStrings("Yesterday");
}
else if (components.hour! >= 2) {
return DateToolsLocalizedStrings("Yesterday")
} else if (components.hour! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!)
}
else if (components.hour! >= 1) {
} else if (components.hour! >= 1) {
if (numericTimes) {
return DateToolsLocalizedStrings("1 hour ago");
return DateToolsLocalizedStrings("1 hour ago")
}
return DateToolsLocalizedStrings("An hour ago");
}
else if (components.minute! >= 2) {
return DateToolsLocalizedStrings("An hour ago")
} else if (components.minute! >= 2) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@minutes ago", value: components.minute!)
}
else if (components.minute! >= 1) {
} else if (components.minute! >= 1) {
if (numericTimes) {
return DateToolsLocalizedStrings("1 minute ago");
return DateToolsLocalizedStrings("1 minute ago")
}
return DateToolsLocalizedStrings("A minute ago");
}
else if (components.second! >= 3) {
return DateToolsLocalizedStrings("A minute ago")
} else if (components.second! >= 3) {
return self.logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!)
}
else {
} else {
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 {
let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year])
let earliest = self.earlierDate(date)
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 yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day
if (components.year! >= 1) {
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!)
}
else if (components.weekOfYear! >= 1) {
} else if (components.weekOfYear! >= 1) {
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!)
}
else if (isYesterday) {
} else if (isYesterday) {
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!)
}
else if (components.minute! >= 1) {
} else if (components.minute! >= 1) {
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!)
}
else {
} else {
return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!)
//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)
let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value)) as! CVarArg) // this may not work, unclear!!
#else
let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value)))
#endif
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]
if (localCode == "ru" || localCode == "uk") {
let XY = Int(floor(value).truncatingRemainder(dividingBy: 100))
let Y = Int(floor(value).truncatingRemainder(dividingBy: 10))
if(Y == 0 || Y > 4 || (XY > 10 && XY < 15)) {
return ""
}
if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) {
if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) {
return "_"
}
if(Y == 1 && XY != 11) {
return "__"
}
}
return ""
}
// MARK: - Localization
private func DateToolsLocalizedStrings(_ string: String) -> String {
//let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle")
//let bundelPath = Bundle(path:classBundle)!
#if os(Linux)
// 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: "")
#endif
}
// MARK: - Date Earlier/Later
/**
* 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
*/
func earlierDate(_ date:Date) -> Date{
func earlierDate(_ date:Date) -> Date {
return (self.timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : 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
*/
func laterDate(_ date:Date) -> Date{
func laterDate(_ date:Date) -> 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
}
/**
* Whether the time period is Open or Closed
*

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

@ -9,51 +9,51 @@
import Foundation
public extension Int {
//MARK: TimePeriod
// MARK: TimePeriod
/**
* A `TimeChunk` with its seconds component set to the value of self
*/
var seconds: TimeChunk {
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
*/
var minutes: TimeChunk {
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
*/
var hours: TimeChunk {
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
*/
var days: TimeChunk {
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
*/
var weeks: TimeChunk {
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
*/
var months: TimeChunk {
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
*/

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.
*/
public struct TimeChunk {
// MARK: - Variables
public var seconds = 0
public var minutes = 0
public var hours = 0
@ -30,9 +30,9 @@ public struct TimeChunk {
public var weeks = 0
public var months = 0
public var years = 0
public init() {}
public init(seconds: Int, minutes: Int, hours: Int, days: Int, weeks: Int, months: Int, years: Int) {
self.seconds = seconds
self.minutes = minutes
@ -42,10 +42,9 @@ public struct TimeChunk {
self.months = months
self.years = years
}
// MARK: - Comparisons
/**
* Check if two `TimeChunk`s are equal.
*
@ -56,10 +55,9 @@ public struct TimeChunk {
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)
}
// MARK: - Conversion
/**
* Generic conversion method. Years are taken to mean
* 365 days. This method should not be used for accurate
@ -124,24 +122,23 @@ public struct TimeChunk {
}
return 0
}
// MARK: - Date Creation
/**
* Returns the current date decreased by the amount in self
*/
public var earlier: Date {
return earlier(than: Date())
}
/**
* Returns the current date increased by the amount in self
*/
public var later: Date {
return later(than: Date())
}
/**
* Returns the given date decreased by the amount in self.
*
@ -152,7 +149,7 @@ public struct TimeChunk {
public func earlier(than date: Date) -> Date {
return date.subtract(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 {
return date.add(self)
}
// MARK: - Lengthen / Shorten
// MARK: In Place
/**
* 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.months = months + chunk.months
newChunk.years = years + chunk.years
return newChunk
}
/**
* 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.months = months - chunk.months
newChunk.years = years - chunk.years
return newChunk
}
// MARK: Mutation
/**
* 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
years += chunk.years
}
/**
* 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
years -= chunk.years
}
// MARK: - Operator Overloads
/**
* Operator overload for adding two `TimeChunk`s
*/
public static func +(leftAddend: TimeChunk, rightAddend: TimeChunk) -> TimeChunk {
return leftAddend.lengthened(by: rightAddend)
}
/**
* Operator overload for subtracting two `TimeChunk`s
*/
public static func -(minuend: TimeChunk, subtrahend: TimeChunk) -> TimeChunk {
return minuend.shortened(by: subtrahend)
}
/**
* Operator overload for checking if two `TimeChunk`s are equal
*/
public static func ==(left: TimeChunk, right: TimeChunk) -> Bool {
return left.equals(chunk: right)
}
/**
* Operator overload for inverting (negating all variables) a `TimeChunk`
*/
public static prefix func -(chunk: TimeChunk) -> TimeChunk {
var invertedChunk = chunk;
var invertedChunk = chunk
invertedChunk.seconds = -chunk.seconds
invertedChunk.minutes = -chunk.minutes
invertedChunk.hours = -chunk.hours
@ -279,5 +274,5 @@ public struct TimeChunk {
invertedChunk.years = -chunk.years
return invertedChunk
}
}

204
Clocker/Dependencies/Date Additions/TimePeriod.swift

@ -8,8 +8,6 @@
import Foundation
/**
* In DateTools, time periods are represented by the TimePeriod protocol.
* 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.
*/
public protocol TimePeriodProtocol {
// MARK: - Variables
/**
* The start date for a TimePeriod representing the starting boundary of the time period
*/
var beginning: Date? {get set}
/**
* The end date for a TimePeriod representing the ending boundary of the time period
*/
@ -33,17 +31,16 @@ public protocol TimePeriodProtocol {
}
public extension TimePeriodProtocol {
// MARK: - Information
/**
* True if the `TimePeriod`'s duration is zero
*/
var isMoment: Bool {
return self.beginning == self.end
}
/**
* The duration of the `TimePeriod` in years.
* Returns the max int if beginning or end are nil.
@ -54,7 +51,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in weeks.
* Returns the max int if beginning or end are nil.
@ -65,7 +62,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in days.
* Returns the max int if beginning or end are nil.
@ -76,7 +73,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in hours.
* Returns the max int if beginning or end are nil.
@ -87,7 +84,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in minutes.
* Returns the max int if beginning or end are nil.
@ -98,7 +95,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in seconds.
* Returns the max int if beginning or end are nil.
@ -109,7 +106,7 @@ public extension TimePeriodProtocol {
}
return Int.max
}
/**
* The duration of the `TimePeriod` in a time chunk.
* 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)
}
/**
* The length of time between the beginning and end dates of the
* `TimePeriod` as a `TimeInterval`.
@ -129,13 +126,12 @@ public extension TimePeriodProtocol {
if self.beginning != nil && self.end != nil {
return abs(self.beginning!.timeIntervalSince(self.end!))
}
return TimeInterval(Double.greatestFiniteMagnitude)
}
// MARK: - Time Period Relationships
/**
* The relationship of the self `TimePeriod` to the given `TimePeriod`.
* 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) {
//Make sure time periods are of positive durations
if (self.beginning!.isEarlier(than: self.end!) && period.beginning!.isEarlier(than: period.end!)) {
//Make comparisons
if (period.end!.isEarlier(than: self.beginning!)) {
return .after
}
else if (period.end!.equals(self.beginning!)) {
} else if (period.end!.equals(self.beginning!)) {
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
else if (period.beginning!.isLater(than: self.end!)) {
} else if (period.beginning!.isLater(than: self.end!)) {
return .before
}
}
}
return .none;
return .none
}
/**
* If `self.beginning` and `self.end` are equal to the beginning and end of the
* given `TimePeriod`.
@ -210,7 +194,7 @@ public extension TimePeriodProtocol {
func equals(_ period: TimePeriodProtocol) -> Bool {
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 end is after `self.end`.
@ -222,7 +206,7 @@ public extension TimePeriodProtocol {
func isInside(of period: TimePeriodProtocol) -> Bool {
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`.
*
@ -234,14 +218,13 @@ public extension TimePeriodProtocol {
func contains(_ date: Date, interval: Interval) -> Bool {
if (interval == .open) {
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 false
}
/**
* If the given `TimePeriod`'s beginning is after `self.beginning` and
* if the given 'TimePeriod`'s after is after `self.end`.
@ -253,7 +236,7 @@ public extension TimePeriodProtocol {
func contains(_ period: TimePeriodProtocol) -> Bool {
return self.beginning!.isEarlierThanOrEqual(to: period.beginning!) && self.end!.isLaterThanOrEqual(to: period.end!)
}
/**
* If self and the given `TimePeriod` share any sub-`TimePeriod`.
*
@ -267,16 +250,16 @@ public extension TimePeriodProtocol {
return true
}
//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
}
//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 false
}
/**
* 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 {
return self.relation(to: period) != .after && self.relation(to: period) != .before
}
/**
* 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 {
return self.isBefore(period: period) || self.isAfter(period: period)
}
/**
* 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 {
if (self.end!.isEarlier(than: period.beginning!)) {
return abs(self.end!.timeIntervalSince(period.beginning!));
}
else if (period.end!.isEarlier(than: self.beginning!)){
return abs(period.end!.timeIntervalSince(self.beginning!));
return abs(self.end!.timeIntervalSince(period.beginning!))
} else if (period.end!.isEarlier(than: self.beginning!)) {
return abs(period.end!.timeIntervalSince(self.beginning!))
}
return 0
}
/**
* The period of time between self and the given `TimePeriod` not contained by either
* as a `TimeChunk`.
@ -330,7 +312,7 @@ public extension TimePeriodProtocol {
}
return nil
}
/**
* 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 {
return self.relation(to: period) == .after
}
/**
* 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 {
return self.relation(to: period) == .before
}
// MARK: - Shifts
//MARK: In Place
// MARK: In Place
/**
* In place, shift the `TimePeriod` by a `TimeInterval`
*
@ -366,7 +348,7 @@ public extension TimePeriodProtocol {
self.beginning?.addTimeInterval(timeInterval)
self.end?.addTimeInterval(timeInterval)
}
/**
* In place, shift the `TimePeriod` by a `TimeChunk`
*
@ -376,12 +358,11 @@ public extension TimePeriodProtocol {
self.beginning = self.beginning?.add(chunk)
self.end = self.end?.add(chunk)
}
// MARK: - Lengthen / Shorten
// MARK: In Place
/**
* In place, lengthen the `TimePeriod`, anchored at the beginning, end or center
*
@ -402,7 +383,7 @@ public extension TimePeriodProtocol {
break
}
}
/**
* In place, lengthen the `TimePeriod`, anchored at the beginning or end
*
@ -423,7 +404,7 @@ public extension TimePeriodProtocol {
break
}
}
/**
* In place, shorten the `TimePeriod`, anchored at the beginning, end or center
*
@ -444,7 +425,7 @@ public extension TimePeriodProtocol {
break
}
}
/**
* 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.
*/
open class TimePeriod: TimePeriodProtocol {
// MARK: - Variables
/**
* The start date for a TimePeriod representing the starting boundary of the time period
*/
public var beginning: Date?
/**
* The end date for a TimePeriod representing the ending boundary of the time period
*/
public var end: Date?
// MARK: - Initializers
init() {
}
init(beginning: Date?, end: Date?) {
self.beginning = beginning
self.end = end
}
init(beginning: Date, duration: TimeInterval) {
self.beginning = beginning
self.end = beginning + duration
}
init(end: Date, duration: TimeInterval) {
self.end = end
self.beginning = end.addingTimeInterval(-duration)
}
init(beginning: Date, chunk: TimeChunk) {
self.beginning = beginning
self.end = beginning + chunk
}
init(end: Date, chunk: TimeChunk) {
self.end = end
self.beginning = end - chunk
}
init(chunk: TimeChunk) {
self.beginning = Date()
self.end = self.beginning?.add(chunk)
}
// MARK: - Shifted
/**
* Shift the `TimePeriod` by a `TimeInterval`
*
@ -540,7 +519,7 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end?.addingTimeInterval(timeInterval)
return timePeriod
}
/**
* Shift the `TimePeriod` by a `TimeChunk`
*
@ -554,11 +533,11 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end?.add(chunk)
return timePeriod
}
// MARK: - Lengthen / Shorten
// MARK: New
/**
* Lengthen the `TimePeriod` by a `TimeInterval`
*
@ -583,10 +562,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = self.end
break
}
return timePeriod
}
/**
* Lengthen the `TimePeriod` by a `TimeChunk`
*
@ -610,10 +589,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end
break
}
return timePeriod
}
/**
* Shorten the `TimePeriod` by a `TimeInterval`
*
@ -638,10 +617,10 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end
break
}
return timePeriod
}
/**
* Shorten the `TimePeriod` by a `TimeChunk`
*
@ -665,20 +644,19 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.end = end
break
}
return timePeriod
}
// MARK: - Operator Overloads
/**
* Operator overload for checking if two `TimePeriod`s are equal
*/
static func ==(leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool {
return leftAddend.equals(rightAddend)
}
// Default anchor = beginning
/**
* Operator overload for lengthening a `TimePeriod` by a `TimeInterval`
@ -686,14 +664,14 @@ open class TimePeriod: TimePeriodProtocol {
static func +(leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning)
}
/**
* Operator overload for lengthening a `TimePeriod` by a `TimeChunk`
*/
static func +(leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning)
}
// Default anchor = beginning
/**
* Operator overload for shortening a `TimePeriod` by a `TimeInterval`
@ -701,14 +679,14 @@ open class TimePeriod: TimePeriodProtocol {
static func -(minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning)
}
/**
* Operator overload for shortening a `TimePeriod` by a `TimeChunk`
*/
static func -(minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning)
}
/**
* 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.
*/
open class TimePeriodChain: TimePeriodGroup {
// MARK: - Chain Existence Manipulation
/**
* Append a TimePeriodProtocol to the periods array and update the Chain's
* beginning and end.
@ -29,20 +29,19 @@ open class TimePeriodChain: TimePeriodGroup {
*/
public func append(_ period: TimePeriodProtocol) {
let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod)
//Update updateExtremes
if periods.count == 1 {
_beginning = period.beginning
_end = period.end
}
else {
} else {
_end = _end?.addingTimeInterval(period.duration)
}
}
/**
* Append a TimePeriodProtocol array to the periods array and update the Chain's
* beginning and end.
@ -52,21 +51,20 @@ open class TimePeriodChain: TimePeriodGroup {
public func append<G: TimePeriodGroup>(contentsOf group: G) {
for period in group.periods {
let beginning = (self.periods.count > 0) ? self.periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod)
//Update updateExtremes
if periods.count == 1 {
_beginning = period.beginning
_end = period.end
}
else {
} else {
_end = _end?.addingTimeInterval(period.duration)
}
}
}
/**
* 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 {
//Insert new period
periods.insert(period, at: index)
}
else if period.beginning != nil && period.end != nil {
} else if period.beginning != nil && period.end != nil {
//Insert new period
periods.insert(period, at: index)
}
else {
} else {
print("All TimePeriods in a TimePeriodChain must contain a defined start and end date")
return
}
//Shift all periods after inserted period
for i in 0..<periods.count {
if i > index && i > 0 {
@ -96,10 +92,10 @@ open class TimePeriodChain: TimePeriodGroup {
periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration)
}
}
updateExtremes()
}
/**
* Remove from period array at the given index.
*
@ -108,17 +104,17 @@ open class TimePeriodChain: TimePeriodGroup {
public func remove(at index: Int) {
//Retrieve duration of period to be removed
let duration = periods[index].duration
//Remove period
periods.remove(at: index)
//Shift all periods after inserted period
for i in index..<periods.count {
periods[i].shift(by: -duration)
}
updateExtremes()
}
/**
* Remove all periods from period array.
*/
@ -126,9 +122,9 @@ open class TimePeriodChain: TimePeriodGroup {
self.periods.removeAll()
updateExtremes()
}
//MARK: - Chain Content Manipulation
// MARK: - Chain Content Manipulation
/**
* 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 {
period.shift(by:duration)
}
_beginning = _beginning?.addingTimeInterval(duration)
_end = _end?.addingTimeInterval(duration)
}
public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform)
}
public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded)
}
internal override func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult)
}
/**
* Removes the last object from the `TimePeriodChain` and returns it
*
@ -162,17 +158,17 @@ open class TimePeriodChain: TimePeriodGroup {
public func pop() -> TimePeriodProtocol? {
let period = self.periods.popLast()
updateExtremes()
return period
}
internal func updateExtremes() {
_beginning = periods.first?.beginning
_end = periods.last?.end
}
// MARK: - Operator Overloads
/**
* 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.
*/
open class TimePeriodCollection: TimePeriodGroup {
// MARK: - Collection Manipulation
/**
* Append a TimePeriodProtocol to the periods array and check if the Collection's
* beginning and end should change.
@ -30,7 +30,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.append(period)
updateExtremes(period: period)
}
/**
* Append a TimePeriodProtocol array to the periods array and check if the Collection's
* beginning and end should change.
@ -43,7 +43,7 @@ open class TimePeriodCollection: TimePeriodGroup {
updateExtremes(period: period)
}
}
/**
* Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's
* beginning and end should change.
@ -56,7 +56,7 @@ open class TimePeriodCollection: TimePeriodGroup {
updateExtremes(period: period)
}
}
/**
* Insert period into periods array at given index.
*
@ -67,7 +67,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.insert(newElement, at: index)
updateExtremes(period: newElement)
}
/**
* Remove from period array at the given index.
*
@ -77,7 +77,7 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.remove(at: at)
updateExtremes()
}
/**
* Remove all periods from period array.
*/
@ -85,10 +85,9 @@ open class TimePeriodCollection: TimePeriodGroup {
periods.removeAll()
updateExtremes()
}
// MARK: - Sorting
// In place
/**
* Sort periods array in place by beginning
@ -106,14 +105,14 @@ open class TimePeriodCollection: TimePeriodGroup {
}
}
}
/**
* Sort periods array in place
*/
public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
self.periods.sort(by: areInIncreasingOrder)
}
// New collection
/**
* Return collection with sorted periods array by beginning
@ -136,7 +135,7 @@ open class TimePeriodCollection: TimePeriodGroup {
collection.append(array)
return collection
}
/**
* Return collection with sorted periods array
*
@ -147,10 +146,9 @@ open class TimePeriodCollection: TimePeriodGroup {
collection.append(self.periods.sorted(by: areInIncreasingOrder))
return collection
}
// MARK: - Collection Relationship
// Potentially use .reduce() instead of these functions
/**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
@ -168,7 +166,7 @@ open class TimePeriodCollection: TimePeriodGroup {
})
return collection
}
/**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing
* the given date.
@ -185,7 +183,7 @@ open class TimePeriodCollection: TimePeriodGroup {
})
return collection
}
/**
* 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`.
@ -202,9 +200,9 @@ open class TimePeriodCollection: TimePeriodGroup {
})
return collection
}
// MARK: - Map
public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection {
var mappedArray = [TimePeriodProtocol]()
mappedArray = try periods.map(transform)
@ -215,18 +213,18 @@ open class TimePeriodCollection: TimePeriodGroup {
}
return mappedCollection
}
// MARK: - Operator Overloads
/**
* Operator overload for comparing `TimePeriodCollection`s to each other
*/
public static func ==(left: TimePeriodCollection, right: TimePeriodCollection) -> Bool {
return left.equals(right)
}
//MARK: - Helpers
// MARK: - Helpers
internal func updateExtremes(period: TimePeriodProtocol) {
//Check incoming period against previous beginning and end date
if self.count == 1 {
@ -236,9 +234,9 @@ open class TimePeriodCollection: TimePeriodGroup {
_beginning = nilOrEarlier(date1: _beginning, date2: period.beginning)
_end = nilOrLater(date1: _end, date2: period.end)
}
}
internal func updateExtremes() {
if periods.count == 0 {
_beginning = nil
@ -252,7 +250,7 @@ open class TimePeriodCollection: TimePeriodGroup {
}
}
}
internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil {
return nil
@ -260,7 +258,7 @@ open class TimePeriodCollection: TimePeriodGroup {
return date1!.earlierDate(date2!)
}
}
internal func nilOrLater(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == 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.
*/
open class TimePeriodGroup: Sequence {
// MARK: - Variables
/**
* The array of periods that define the group.
*/
internal var periods: [TimePeriodProtocol] = []
internal var _beginning: Date?
internal var _end: Date?
/**
* The earliest beginning date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil beginning date (indefinite).
@ -35,7 +35,7 @@ open class TimePeriodGroup: Sequence {
public var beginning: Date? {
return _beginning
}
/**
* The latest end date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil end date (indefinite).
@ -44,14 +44,14 @@ open class TimePeriodGroup: Sequence {
public var end: Date? {
return _end
}
/**
* The number of periods in the periods array.
*/
public var count: Int {
return periods.count
}
/**
* 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.
@ -62,15 +62,15 @@ open class TimePeriodGroup: Sequence {
}
return nil
}
// MARK: - Initializers
public init() {
}
// MARK: - Comparisons
/**
* 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 {
return containSameElements(array1: self.periods, group.periods)
}
// MARK: - Sequence Protocol
public func makeIterator() -> IndexingIterator<Array<TimePeriodProtocol>> {
return periods.makeIterator()
}
public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform)
}
public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded)
}
public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows {
return try periods.forEach(body)
}
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)
}
subscript(index: Int) -> TimePeriodProtocol {
get {
return periods[index]
}
}
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult)
}
internal func containSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool {
guard array1.count == array2.count else {
return false // No need to sorting if they already have different counts
}
var compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil {
return false

99
Clocker/Dependencies/Solar.swift

@ -4,13 +4,13 @@ import Cocoa
import CoreLocation
public struct Solar {
/// The coordinate that is used for the calculation
public let coordinate: CLLocationCoordinate2D
/// The date to generate sunrise / sunset times for
public fileprivate(set) var date: Date
public fileprivate(set) var sunrise: Date?
public fileprivate(set) var sunset: Date?
public fileprivate(set) var civilSunrise: Date?
@ -19,24 +19,24 @@ public struct Solar {
public fileprivate(set) var nauticalSunset: Date?
public fileprivate(set) var astronomicalSunrise: Date?
public fileprivate(set) var astronomicalSunset: Date?
// MARK: Init
public init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) {
self.date = date
guard CLLocationCoordinate2DIsValid(coordinate) else {
return nil
}
self.coordinate = coordinate
// Fill this Solar object with relevant data
calculate()
}
// MARK: - Public functions
/// 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.
public mutating func calculate() {
@ -49,14 +49,14 @@ public struct Solar {
astronomicalSunrise = calculate(.sunrise, for: date, and: .astronimical)
astronomicalSunset = calculate(.sunset, for: date, and: .astronimical)
}
// MARK: - Private functions
fileprivate enum SunriseSunset {
case sunrise
case sunset
}
/// Used for generating several of the possible sunrise / sunset times
fileprivate enum Zenith: Double {
case official = 90.83
@ -64,85 +64,85 @@ public struct Solar {
case nautical = 102
case astronimical = 108
}
fileprivate func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? {
guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil }
// Get the day of the year
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = utcTimezone
guard let dayInt = calendar.ordinality(of: .day, in: .year, for: date) else { return nil }
let day = Double(dayInt)
// Convert longitude to hour value and calculate an approx. time
let lngHour = coordinate.longitude / 15
let hourTime: Double = sunriseSunset == .sunrise ? 6 : 18
let t = day + ((hourTime - lngHour) / 24)
// Calculate the suns mean anomaly
let M = (0.9856 * t) - 3.289
// Calculate the sun's true longitude
let subexpression1 = 1.916 * sin(M.degreesToRadians)
let subexpression2 = 0.020 * sin(2 * M.degreesToRadians)
var L = M + subexpression1 + subexpression2 + 282.634
// Normalise L into [0, 360] range
L = normalise(L, withMaximum: 360)
// Calculate the Sun's right ascension
var RA = atan(0.91764 * tan(L.degreesToRadians)).radiansToDegrees
// Normalise RA into [0, 360] range
RA = normalise(RA, withMaximum: 360)
// Right ascension value needs to be in the same quadrant as L...
let Lquadrant = floor(L / 90) * 90
let RAquadrant = floor(RA / 90) * 90
RA = RA + (Lquadrant - RAquadrant)
// Convert RA into hours
RA = RA / 15
// Calculate Sun's declination
let sinDec = 0.39782 * sin(L.degreesToRadians)
let cosDec = cos(asin(sinDec))
// Calculate the Sun's local hour angle
let cosH = (cos(zenith.rawValue.degreesToRadians) - (sinDec * sin(coordinate.latitude.degreesToRadians))) / (cosDec * cos(coordinate.latitude.degreesToRadians))
// No sunrise
guard cosH < 1 else {
return nil
}
// No sunset
guard cosH > -1 else {
return nil
}
// Finish calculating H and convert into hours
let tempH = sunriseSunset == .sunrise ? 360 - acos(cosH).radiansToDegrees : acos(cosH).radiansToDegrees
let H = tempH / 15.0
// Calculate local mean time of rising
let T = H + RA - (0.06571 * t) - 6.622
// Adjust time back to UTC
var UT = T - lngHour
// Normalise UT into [0, 24] range
UT = normalise(UT, withMaximum: 24)
// Calculate all of the sunrise's / sunset's date components
let hour = floor(UT)
let minute = floor((UT - hour) * 60.0)
let second = (((UT - hour) * 60) - minute) * 60.0
let shouldBeYesterday = lngHour > 0 && UT > 12 && sunriseSunset == .sunrise
let shouldBeTomorrow = lngHour < 0 && UT < 12 && sunriseSunset == .sunset
let setDate: Date
if shouldBeYesterday {
setDate = Date(timeInterval: -(60 * 60 * 24), since: date)
@ -151,35 +151,35 @@ public struct Solar {
} else {
setDate = date
}
var components = calendar.dateComponents([.day, .month, .year], from: setDate)
components.hour = Int(hour)
components.minute = Int(minute)
components.second = Int(second)
calendar.timeZone = utcTimezone
return calendar.date(from: components)
}
/// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum`
fileprivate func normalise(_ value: Double, withMaximum maximum: Double) -> Double {
var value = value
if value < 0 {
value += maximum
}
if value > maximum {
value -= maximum
}
return value
}
}
extension Solar {
/// Whether the location specified by the `latitude` and `longitude` is in daytime on `date`
/// - Complexity: O(1)
public var isDaytime: Bool {
@ -189,23 +189,23 @@ extension Solar {
else {
return false
}
let beginningOfDay = sunrise.timeIntervalSince1970
let endOfDay = sunset.timeIntervalSince1970
let currentTime = self.date.timeIntervalSince1970
let isSunriseOrLater = currentTime >= beginningOfDay
let isBeforeSunset = currentTime < endOfDay
return isSunriseOrLater && isBeforeSunset
}
/// Whether the location specified by the `latitude` and `longitude` is in nighttime on `date`
/// - Complexity: O(1)
public var isNighttime: Bool {
return !isDaytime
}
}
// MARK: - Helper extensions
@ -214,9 +214,8 @@ private extension Double {
var degreesToRadians: Double {
return Double(self) * (Double.pi / 180.0)
}
var radiansToDegrees: Double {
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 {
self.saveDefaultIdentifiersList()
}
completionHandler(granted)
}
}
@ -146,12 +146,12 @@ extension EventCenter {
print("Unable to filter events because user hasn't selected calendars")
}
func saveDefaultIdentifiersList() {
OperationQueue.main.addOperation { [weak self] in
guard let `self` = self else { return }
let allCalendars = self.retrieveAllCalendarIdentifiers()
if !allCalendars.isEmpty {
UserDefaults.standard.set(allCalendars, forKey: CLSelectedCalendars)
print("Finished saving all calendar identifiers in default")
@ -165,7 +165,7 @@ extension EventCenter {
return calendar.calendarIdentifier
}
}
func fetchEvents(_ start: Int, _ end: Int) {
if calendarAccessDenied() || calendarAccessNotDetermined() {
print("Refetching aborted because we don't have permission!")
@ -258,9 +258,7 @@ extension EventCenter {
for date in eventsForDateMapper.keys {
let sortedEvents = eventsForDateMapper[date]?.sorted(by: { (e1, e2) -> Bool in
if e1.isAllDay { return true }
else if e2.isAllDay { return false }
else { return e1.event.startDate < e2.event.startDate }
if e1.isAllDay { return true } else if e2.isAllDay { return false } else { return e1.event.startDate < e2.event.startDate }
})
eventsForDateMapper[date] = sortedEvents
}

24
Clocker/Menu Bar/MenubarHandler.swift

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

66
Clocker/Menu Bar/StatusContainerView.swift

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

24
Clocker/Menu Bar/StatusItemHandler.swift

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

31
Clocker/Menu Bar/StatusItemView.swift

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

2
Clocker/Onboarding/FinalOnboardingViewController.swift

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

2
Clocker/Onboarding/OnboardingController.swift

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

32
Clocker/Onboarding/OnboardingParentViewController.swift

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

10
Clocker/Onboarding/OnboardingPermissionsViewController.swift

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

88
Clocker/Onboarding/OnboardingSearchController.swift

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

64
Clocker/Overall App/AppDefaults.swift

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

18
Clocker/Overall App/AppKit + Additions.swift

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

22
Clocker/Overall App/DataStore.swift

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

6
Clocker/Overall App/DateFormatterManager.swift

@ -45,7 +45,7 @@ class DateFormatterManager: NSObject {
specializedFormatter.locale = locale
return specializedFormatter
}
@objc class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale: Locale = Locale.autoupdatingCurrent) -> DateFormatter {
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none
@ -54,7 +54,7 @@ class DateFormatterManager: NSObject {
dateFormatter.timeZone = TimeZone(identifier: timezoneIdentifier)
return dateFormatter
}
@objc class func localizedCalendaricalDateFormatter(with format: String) -> DateFormatter {
calendarDateFormatter.dateStyle = .none
calendarDateFormatter.timeStyle = .none
@ -63,7 +63,7 @@ class DateFormatterManager: NSObject {
calendarDateFormatter.calendar = gregorianCalendar
return calendarDateFormatter
}
@objc class func localizedSimpleFormatter(_ format: String) -> DateFormatter {
localizedSimpleFormatter.dateStyle = .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)
return error
}()
static let unableToGenerateURL: NSError = {
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."]

34
Clocker/Overall App/Themer.swift

@ -6,7 +6,7 @@ func isDarkModeOn() -> Bool {
if #available(macOS 10.14, *) {
return NSAppearance.current.name == NSAppearance.Name.darkAqua
}
return false
}
@ -42,11 +42,11 @@ class Themer: NSObject {
default:
themeIndex = Theme.light
}
super.init()
setAppAppearance()
DistributedNotificationCenter.default.addObserver(self,
selector: #selector(respondToInterfaceStyle),
name: .interfaceStyleDidChange,
@ -74,17 +74,17 @@ extension Themer {
default:
themeIndex = Theme.light
}
setAppAppearance()
}
@objc func respondToInterfaceStyle() {
OperationQueue.main.addOperation {
self.setAppAppearance()
}
}
private func setAppAppearance() {
if #available(OSX 10.14, *) {
var appAppearance = NSAppearance(named: .aqua)
@ -270,7 +270,7 @@ extension Themer {
return themeIndex == .light ? NSImage(named: NSImage.Name("Extra"))! : NSImage(named: NSImage.Name("ExtraWhite"))!
}
func menubarOnboardingImage() -> NSImage {
if #available(macOS 10.14, *) {
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"))!
}
@ -315,7 +315,7 @@ extension Themer {
}
func currentLocationImage() -> NSImage {
if #available(macOS 10.14, *) {
switch themeIndex {
case .light:
@ -326,7 +326,7 @@ extension Themer {
return NSImage(named: NSImage.Name("CurrentLocationDynamic"))!
}
}
return themeIndex == .light ? NSImage(named: NSImage.Name("CurrentLocation"))! : NSImage(named: NSImage.Name("CurrentLocationWhite"))!
}
@ -365,7 +365,7 @@ extension Themer {
}
func privacyTabImage() -> NSImage {
if #available(macOS 10.14, *) {
switch themeIndex {
case .light:
@ -376,12 +376,12 @@ extension Themer {
return NSImage(named: .permissionTabIcon)!
}
}
return themeIndex == .light ? NSImage(named: NSImage.Name("Privacy"))! : NSImage(named: NSImage.Name("Privacy Dark"))!
}
func appearanceTabImage() -> NSImage {
if #available(macOS 10.14, *) {
switch themeIndex {
case .light:
@ -392,12 +392,12 @@ extension Themer {
return NSImage(named: .appearanceTabIcon)!
}
}
return themeIndex == .light ? NSImage(named: NSImage.Name("Appearance"))! : NSImage(named: NSImage.Name("Appearance Dark"))!
}
func calendarTabImage() -> NSImage {
if #available(macOS 10.14, *) {
switch themeIndex {
case .light:
@ -408,7 +408,7 @@ extension Themer {
return NSImage(named: .calendarTabIcon)!
}
}
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!
class TimezoneData: NSObject, NSCoding {
enum SelectionType: Int {
case city
case timezone
@ -28,7 +28,7 @@ class TimezoneData: NSObject, NSCoding {
case twentyFourFormat
case globalFormat
}
enum SecondsOverride: Int {
case yes
case no
@ -170,7 +170,7 @@ class TimezoneData: NSObject, NSCoding {
let override = aDecoder.decodeInteger(forKey: "overrideFormat")
overrideFormat = TimezoneOverride(rawValue: override)!
let secondsOverride = aDecoder.decodeInteger(forKey: "secondsOverrideFormat")
overrideSecondsFormat = SecondsOverride(rawValue: secondsOverride)!
}
@ -189,20 +189,20 @@ class TimezoneData: NSObject, NSCoding {
return nil
}
private class func logOldModelUsage() {
guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else {
return
}
let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion
let osVersion = "\(operatingSystem.majorVersion).\(operatingSystem.minorVersion).\(operatingSystem.patchVersion)"
let versionInfo = "Clocker \(shortVersion) (\(appVersion))"
let feedbackInfo = [
AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo,
AppFeedbackConstants.CLClockerVersion: versionInfo
]
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(overrideFormat.rawValue, forKey: "overrideFormat")
aCoder.encode(overrideSecondsFormat.rawValue, forKey: "secondsOverrideFormat")
}
@ -335,7 +335,7 @@ class TimezoneData: NSObject, NSCoding {
overrideFormat = .globalFormat
}
}
func setShouldOverrideSecondsFormat(_ shouldOverride: Int) {
if shouldOverride == 0 {
overrideSecondsFormat = .yes
@ -361,7 +361,7 @@ class TimezoneData: NSObject, NSCoding {
let errorDictionary = [
"Formatted Address": name,
"Place Identifier": placeIdentifier,
"TimezoneID": timezoneIdentifier,
"TimezoneID": timezoneIdentifier
]
Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData")
@ -396,15 +396,15 @@ class TimezoneData: NSObject, NSCoding {
return timeFormat
}
func shouldDisplayTwelveHourFormat() -> Bool {
if overrideSecondsFormat == .globalFormat {
return DataStore.shared().shouldDisplay(.twelveHour)
}
return overrideFormat == .twelveHourFormat
}
func shouldShowSeconds() -> Bool {
if overrideSecondsFormat == .globalFormat {
return DataStore.shared().shouldDisplay(.seconds)

52
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

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

38
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -3,12 +3,12 @@
import Cocoa
class NotesPopover: NSViewController {
private enum OverrideType {
case timezoneFormat
case seconds
}
var dataObject: TimezoneData?
var timezoneObjects: [Data]?
@ -38,7 +38,7 @@ class NotesPopover: NSViewController {
@IBOutlet var timeFormatControl: NSSegmentedControl!
@IBOutlet weak var secondsFormatControl: NSSegmentedControl!
@IBOutlet var notesTextView: TextViewWithPlaceholder!
override func viewDidLoad() {
@ -62,7 +62,7 @@ class NotesPopover: NSViewController {
"1 hour before",
"2 hour before",
"1 day before",
"2 days before",
"2 days before"
]
alertPopupButton.removeAllItems()
@ -200,7 +200,7 @@ class NotesPopover: NSViewController {
let attributesDictionary = [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.paragraphStyle: style,
NSAttributedString.Key.paragraphStyle: style
]
button.attributedTitle = NSAttributedString(string: title,
@ -268,7 +268,7 @@ class NotesPopover: NSViewController {
updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat)
updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .timezoneFormat)
refreshMainTableView()
// Update the display if the chosen menubar mode is compact!
if let delegate = NSApplication.shared.delegate as? AppDelegate {
let handler = delegate.statusItemForPanel()
@ -288,24 +288,24 @@ class NotesPopover: NSViewController {
private func updateMenubarTitles() {
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
if let convertedObject = TimezoneData.customObject(from: menubarLocation) {
return convertedObject == dataObject
}
return false
}
if let index = menubarIndex {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: model)
timezones[index] = encodedObject
UserDefaults.standard.set(timezones, forKey: CLMenubarFavorites)
}
}
@ -452,19 +452,19 @@ class NotesPopover: NSViewController {
updateContent()
}
}
@IBAction func customizeSecondsFormat(_ sender: NSSegmentedControl) {
updateTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds)
updateMenubarTimezoneInDefaultPreferences(with: sender.selectedSegment, .seconds)
refreshMainTableView()
// Update the display if the chosen menubar mode is compact!
if let delegate = NSApplication.shared.delegate as? AppDelegate {
let handler = delegate.statusItemForPanel()
handler.setupStatusItem()
}
}
}
@objc extension NotesPopover {
@ -513,7 +513,7 @@ class NotesPopover: NSViewController {
setInitialReminderTime()
updateTimeFormat()
updateSecondsFormat()
}
@ -526,7 +526,7 @@ class NotesPopover: NSViewController {
timeFormatControl.setSelected(true, forSegment: 2)
}
}
private func updateSecondsFormat() {
if dataObject?.overrideSecondsFormat.rawValue == 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) {
let textDict = [
NSAttributedString.Key.foregroundColor: NSColor.gray,
NSAttributedString.Key.font: placeHolderFont,
NSAttributedString.Key.font: placeHolderFont
]
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() {
super.updateDefaultPreferences()
}
func setFrameTheNewWay(_ rect: NSRect, _ maxX: CGFloat) {
// Calculate window's top left point.
// First, center window under status item.
let w = CGFloat(NSWidth((window?.frame)!))
var x = CGFloat(roundf(Float(NSMidX(rect) - w / 2)))
let y = CGFloat(NSMinY(rect) - 2)
var x = CGFloat(roundf(Float(rect.midX - w / 2)))
let y = CGFloat(rect.minY - 2)
let kMinimumSpaceBetweenWindowAndScreenEdge: CGFloat = 10
if x + w + kMinimumSpaceBetweenWindowAndScreenEdge > maxX {
x = maxX - w - kMinimumSpaceBetweenWindowAndScreenEdge
}
window?.setFrameTopLeftPoint(NSMakePoint(x, y))
window?.setFrameTopLeftPoint(NSPoint(x: x, y: y))
window?.invalidateShadow()
}
@ -99,20 +99,20 @@ class PanelController: ParentPanelController {
// New way to set the panel's frame.
// This takes into account the screen's dimensions.
private func setPanelFrame() {
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else {
return
}
var statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.view?.window
var statusView = appDelegate.statusItemForPanel().statusItem.view
// This below is a better way than actually checking if the menubar compact mode is set.
if statusBackgroundWindow == nil || statusView == nil {
statusBackgroundWindow = appDelegate.statusItemForPanel().statusItem.button?.window
statusView = appDelegate.statusItemForPanel().statusItem.button
}
if let statusWindow = statusBackgroundWindow,
let statusButton = statusView {
var statusItemFrame = statusWindow.convertToScreen(statusButton.frame)
@ -175,16 +175,16 @@ class PanelController: ParentPanelController {
"Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No",
"Country": country,
"Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No",
"Number of Timezones": preferences.count,
"Number of Timezones": preferences.count
]
Logger.log(object: panelEvent, for: "openedPanel")
}
private func startWindowTimer() {
stopMenubarTimerIfNeccesary()
if let timer = parentTimer, timer.state == .paused {
parentTimer?.start()
return
@ -194,9 +194,9 @@ class PanelController: ParentPanelController {
}
private func startTimer() {
print("Start timer called")
parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in
OperationQueue.main.addOperation {
self.updateTime()
@ -205,14 +205,14 @@ class PanelController: ParentPanelController {
parentTimer!.start()
}
private func stopMenubarTimerIfNeccesary() {
let count = (DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data])?.count ?? 0
if count >= 1 || DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
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 ")
delegate.invalidateMenubarTimer(false)
}
}

22
Clocker/Panel/ParentPanelController.swift

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

26
Clocker/Panel/Rate Controller/RateController.swift

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

2
Clocker/Panel/UI/PanelTableView.swift

@ -31,7 +31,7 @@ class PanelTableView: NSTableView {
let options: NSTrackingArea.Options = [
.mouseMoved,
.mouseEnteredAndExited,
.activeAlways,
.activeAlways
]
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,
andButton: sender)
}
Logger.log(object: [:], for: "Open Extra Options")
}
override func mouseDown(with _: NSEvent) {
window?.endEditing(for: nil)
}
override func rightMouseDown(with event: NSEvent) {
super.rightMouseDown(with: event)
showExtraOptions(extraOptions)

22
Clocker/Preferences/About/AboutViewController.swift

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

2
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

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

57
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -12,7 +12,7 @@ class AppearanceViewController: ParentViewController {
@IBOutlet weak var includeDayInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includeDateInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includePlaceNameControl: NSSegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
@ -31,16 +31,16 @@ class AppearanceViewController: ParentViewController {
"6 days",
"7 days"
])
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.animateBackgroundColorChange()
self.view.needsDisplay = true // Let's make the color change permanent.
}
}
private func animateBackgroundColorChange() {
let colorAnimation = CABasicAnimation(keyPath: "backgroundColor")
colorAnimation.duration = 0.25
@ -60,18 +60,18 @@ class AppearanceViewController: ParentViewController {
if let selectedIndex = DataStore.shared().retrieve(key: CLFutureSliderRange) as? NSNumber {
sliderDayRangePopup.selectItem(at: selectedIndex.intValue)
}
if #available(macOS 10.14, *) {
theme.setEnabled(true, forSegment: 2)
} else {
theme.setEnabled(false, forSegment: 2)
}
let shouldDisplayCompact = DataStore.shared().shouldDisplay(.menubarCompactMode)
menubarMode.setSelected(true, forSegment: shouldDisplayCompact ? 0 : 1)
updateMenubarControls(!shouldDisplayCompact)
}
@IBOutlet weak var headerLabel: NSTextField!
@IBOutlet weak var timeFormatLabel: NSTextField!
@IBOutlet weak var panelTheme: NSTextField!
@ -87,7 +87,7 @@ class AppearanceViewController: ParentViewController {
@IBOutlet weak var menubarDisplayOptionsLabel: NSTextField!
@IBOutlet weak var appDisplayLabel: NSTextField!
@IBOutlet weak var menubarModeLabel: NSTextField!
private func setup() {
headerLabel.stringValue = "Main Panel Options"
timeFormatLabel.stringValue = "Time Format"
@ -103,7 +103,7 @@ class AppearanceViewController: ParentViewController {
includePlaceLabel.stringValue = "Include Place Name"
menubarDisplayOptionsLabel.stringValue = "Menubar Display Options"
menubarModeLabel.stringValue = "Menubar Mode"
[headerLabel, timeFormatLabel, panelTheme, dayDisplayOptionsLabel, showSliderLabel, showSecondsLabel, showSunriseLabel, largerTextLabel, futureSliderRangeLabel, includeDayLabel, includeDateLabel, includePlaceLabel, menubarDisplayOptionsLabel, appDisplayLabel, menubarModeLabel].forEach {
$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")
refresh(panel: true, floating: true)
updateStatusItem()
}
private var previousBackgroundColor: NSColor = NSColor.white
@IBAction func themeChanged(_ sender: NSSegmentedControl) {
previousBackgroundColor = Themer.shared().mainBackgroundColor()
Themer.shared().set(theme: sender.selectedSegment)
refresh(panel: false, floating: true)
@ -194,9 +194,9 @@ class AppearanceViewController: ParentViewController {
updateStatusItem()
}
@IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) {
if sender.selectedSegment == 0 {
Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode")
NSApp.setActivationPolicy(.accessory)
@ -205,7 +205,6 @@ class AppearanceViewController: ParentViewController {
NSApp.setActivationPolicy(.regular)
}
}
private func refresh(panel: Bool, floating: Bool) {
OperationQueue.main.addOperation {
@ -234,54 +233,52 @@ class AppearanceViewController: ParentViewController {
}
}
}
@IBAction func displayDayInMenubarAction(_ sender: Any) {
DataStore.shared().updateDayPreference()
updateStatusItem()
}
@IBAction func displayDateInMenubarAction(_ sender: Any) {
updateStatusItem()
}
@IBAction func displayPlaceInMenubarAction(_ sender: Any) {
updateStatusItem()
}
private func updateStatusItem() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return
}
if DataStore.shared().shouldDisplay(.menubarCompactMode) {
statusItem.setupStatusItem()
} else {
statusItem.performTimerWork()
}
}
@IBAction func menubarModeChanged(_ sender: NSSegmentedControl) {
updateMenubarControls(sender.selectedSegment == 1)
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return
}
statusItem.setupStatusItem()
if sender.selectedSegment == 0 {
Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Compact Mode")
} else {
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.
// Disable those options to let the user know.
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
class ClockerTextBackgroundView: NSView {
override func awakeFromNib() {
wantsLayer = true
layer?.cornerRadius = 8.0
layer?.masksToBounds = false
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
}
}
override func updateLayer() {
super.updateLayer()
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
@ -23,7 +23,7 @@ class ClockerTextBackgroundView: NSView {
}
class CalendarViewController: ParentViewController {
@IBOutlet var showSegmentedControl: NSSegmentedControl!
@IBOutlet var allDaysSegmentedControl: NSSegmentedControl!
@IBOutlet var truncateTextField: NSTextField!
@ -31,35 +31,35 @@ class CalendarViewController: ParentViewController {
@IBOutlet var informationField: NSTextField!
@IBOutlet var grantAccessButton: NSButton!
@IBOutlet weak var calendarsTableView: NSTableView!
@IBOutlet weak var showNextMeetingInMenubarControl: NSSegmentedControl!
@IBOutlet weak var backgroundView: NSView!
@IBOutlet weak var nextMeetingBackgroundView: NSView!
private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars()
override func viewDidLoad() {
super.viewDidLoad()
setup()
NotificationCenter.default.addObserver(self,
selector: #selector(calendarAccessStatusChanged),
name: .calendarAccessGranted,
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()
}
if #available(macOS 10.14, *) {
noAccessView.material = .underWindowBackground
}
}
@objc func calendarAccessStatusChanged() {
verifyCalendarAccess()
view.window?.windowController?.showWindow(nil)
view.window?.makeKeyAndOrderFront(nil)
}
@ -74,7 +74,7 @@ class CalendarViewController: ParentViewController {
} else {
showSegmentedControl.selectedSegment = 1
}
// 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))
}
@ -129,32 +129,32 @@ class CalendarViewController: ParentViewController {
NSWorkspace.shared.launchApplication("System Preferences")
}
}
@IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) {
// We need to start the menubar timer if it hasn't been started already
guard let delegate = NSApplication.shared.delegate as? AppDelegate else {
assertionFailure()
return
}
let statusItemHandler = delegate.statusItemForPanel()
if sender.selectedSegment == 0 {
if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true {
print("Timer is already in progress")
updateStatusItem()
return
}
} else {
statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false)
}
}
@IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) {
var showUpcomingEventView = "YES"
@ -179,15 +179,15 @@ class CalendarViewController: ParentViewController {
Logger.log(object: ["Show": "YES"], for: "Upcoming Event View")
}
}
private func updateStatusItem() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return
}
statusItem.performTimerWork()
}
@IBOutlet weak var headerLabel: NSTextField!
@IBOutlet weak var upcomingEventView: NSTextField!
@IBOutlet weak var allDayMeetingsLabel: NSTextField!
@ -197,7 +197,7 @@ class CalendarViewController: ParentViewController {
@IBOutlet weak var showEventsFromLabel: NSTextField!
@IBOutlet weak var charactersField: NSTextField!
@IBOutlet weak var truncateAccessoryLabel: NSTextField!
private func setup() {
// Grant access button's text color is taken care above.
headerLabel.stringValue = "Upcoming Event View Options"
@ -208,7 +208,7 @@ class CalendarViewController: ParentViewController {
charactersField.stringValue = "characters"
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...\""
[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 {
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return false
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else {
return 30.0
}
return 24.0
}
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 {
message.sourceName.stringValue = currentSource
return message
}
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.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off
@ -253,16 +252,16 @@ extension CalendarViewController: NSTableViewDelegate {
calendarCell.calendarSelected.action = #selector(calendarSelected(_:))
return calendarCell
}
return nil
}
@objc func calendarSelected(_ checkbox: NSButton) {
let currentSelection = checkbox.tag
var sourcesAndCalendars = calendars
if var calInfo = sourcesAndCalendars[currentSelection] as? CalendarInfo {
calInfo.selected = (checkbox.state == .on)
sourcesAndCalendars[currentSelection] = calInfo
@ -270,21 +269,21 @@ extension CalendarViewController: NSTableViewDelegate {
updateSelectedCalendars(sourcesAndCalendars)
}
private func updateSelectedCalendars(_ selection: [Any]) {
var selectedCalendars: [String] = []
for obj in selection {
if let calInfo = obj as? CalendarInfo, calInfo.selected {
selectedCalendars.append(calInfo.calendar.calendarIdentifier)
}
}
UserDefaults.standard.set(selectedCalendars, forKey: CLSelectedCalendars)
calendars = EventCenter.sharedCenter().fetchSourcesAndCalendars()
EventCenter.sharedCenter().filterEvents()
}
}
@ -297,5 +296,3 @@ class CalendarTableViewCell: NSTableCellView {
@IBOutlet var calendarName: NSTextField!
@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 additionalSortOptions: NSView!
@IBOutlet weak var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton!
@IBOutlet var headerLabel: NSTextField!
@ -198,7 +198,7 @@ class PreferencesViewController: ParentViewController {
}
UserDefaults.standard.set(archivedObjects, forKey: CLMenubarFavorites)
// Update appereance if in compact menubar mode
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer()
@ -240,7 +240,7 @@ class PreferencesViewController: ParentViewController {
[timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach {
$0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [
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" {
return "GMT+12"
}
return NSTimeZone(name: timezoneFilteredArray[row])?.abbreviation ?? "Error"
}
@ -387,7 +387,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if timezoneArray[row] == "UTC" {
return "UTC"
}
if timezoneArray[row] == "Anywhere on Earth" {
return "AoE"
}
@ -410,7 +410,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if selectedTimeZones.count > row {
Logger.log(object: [
"Old Label": dataObject.customLabel ?? "Error",
"New Label": formattedValue,
"New Label": formattedValue
],
for: "Custom Label Changed")
@ -423,7 +423,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
Logger.log(object: [
"MethodName": "SetObjectValue",
"Selected Timezone Count": selectedTimeZones.count,
"Current Row": row,
"Current Row": row
],
for: "Error in selected row count")
}
@ -432,11 +432,11 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
insert(timezone: dataObject, at: row)
if dataObject.isFavourite == 1, let menubarTitles = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data] {
var mutableArray = menubarTitles
let archivedObject = NSKeyedArchiver.archivedData(withRootObject: dataObject)
mutableArray.append(archivedObject)
UserDefaults.standard.set(mutableArray, forKey: CLMenubarFavorites)
if dataObject.customLabel != nil {
@ -446,7 +446,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer()
}
if mutableArray.count > 1 {
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 {
appDelegate.invalidateMenubarTimer(true)
}
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.setupMenubarTimer()
}
@ -487,43 +487,43 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
}
private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() {
let isUITestRunning = ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument)
// If we have seen displayed the message before, abort!
let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: CLLongStatusBarWarningMessage)
if haveWeSeenThisMessageBefore && !isUITestRunning {
return
}
// If the user is already using the compact mode, abort.
if DataStore.shared().shouldDisplay(.menubarCompactMode) && !isUITestRunning {
return
}
// Time to display the alert.
NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert()
alert.showsSuppressionButton = true
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.addButton(withTitle: "Enable Compact Mode")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
if response.rawValue == 1000 {
OperationQueue.main.addOperation {
UserDefaults.standard.set(0, forKey: CLMenubarCompactMode)
if alert.suppressionButton?.state == NSControl.StateValue.on {
UserDefaults.standard.set(true, forKey: CLLongStatusBarWarningMessage)
}
self.updateStatusBarAppearance()
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,
completionHandler: { [weak self] response, error in
guard let `self` = self else { return }
OperationQueue.main.addOperation {
@ -696,7 +696,7 @@ extension PreferencesViewController {
CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId,
CLPlaceIdentifier: result.placeId
] as [String: Any]
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)"
NetworkManager.task(with: urlString) { [weak self] response, error in
guard let `self` = self else { return }
OperationQueue.main.addOperation {
@ -793,7 +793,7 @@ extension PreferencesViewController {
"latitude": dataObject.latitude!,
"longitude": dataObject.longitude!,
"nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress,
CLCustomLabel: filteredAddress
] as [String: Any]
let timezoneObject = TimezoneData(with: newTimeZone)
@ -951,7 +951,7 @@ extension PreferencesViewController {
}
let currentSelection = timezoneFilteredArray[availableTimezoneTableView.selectedRow]
let metaInfo = metadata(for: currentSelection)
data.timezoneID = metaInfo.0
data.formattedAddress = metaInfo.1
@ -992,7 +992,7 @@ extension PreferencesViewController {
isActivityInProgress = false
}
}
private func metadata(for selection: String) -> (String, String) {
if selection == "Anywhere on Earth" {
return ("GMT-1200", selection)
@ -1072,7 +1072,7 @@ extension PreferencesViewController {
if selectedTimeZones.count == 0 {
UserDefaults.standard.set(nil, forKey: CLMenubarFavorites)
}
updateStatusBarAppearance()
updateStatusItem()
@ -1086,12 +1086,12 @@ extension PreferencesViewController {
statusItem.performTimerWork()
}
private func updateStatusBarAppearance() {
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
return
}
statusItem.setupStatusItem()
}

40
Clocker/Preferences/OneWindowController.swift

@ -3,35 +3,35 @@
import Cocoa
class CenteredTabViewController: NSTabViewController {
override func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
super.toolbarDefaultItemIdentifiers(toolbar)
var toolbarItems: [NSToolbarItem.Identifier] = [NSToolbarItem.Identifier.flexibleSpace]
tabViewItems.forEach { (item) in
if let identifier = item.identifier as? String {
toolbarItems.append(NSToolbarItem.Identifier.init(identifier))
}
}
toolbarItems.append(NSToolbarItem.Identifier.flexibleSpace)
return toolbarItems
}
}
class OneWindowController: NSWindowController {
private static var sharedWindow: OneWindowController!
override func windowDidLoad() {
super.windowDidLoad()
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
context.duration = 1
context.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeOut)
@ -41,17 +41,17 @@ class OneWindowController: NSWindowController {
self.setupToolbarImages()
}
}
private func setup() {
setupWindow()
setupToolbarImages()
}
private func setupWindow() {
window?.titlebarAppearsTransparent = true
window?.backgroundColor = Themer.shared().mainBackgroundColor()
}
class func shared() -> OneWindowController {
if (sharedWindow == nil) {
let prefStoryboard = NSStoryboard.init(name: "Preferences", bundle: nil)
@ -59,33 +59,33 @@ class OneWindowController: NSWindowController {
}
return sharedWindow
}
func openPermissions() {
guard let window = window else {
return
}
if !(window.isMainWindow) || !(window.isVisible) {
showWindow(nil)
}
guard let tabViewController = contentViewController as? CenteredTabViewController else {
return
}
tabViewController.selectedTabViewItemIndex = 3
}
private func setupToolbarImages() {
guard let tabViewController = contentViewController as? CenteredTabViewController else {
return
}
let themer = Themer.shared()
let identifierTOImageMapping: [String: NSImage] = ["Appearance": themer.appearanceTabImage(),
"Calendar": themer.calendarTabImage(),
"Permissions": themer.privacyTabImage()]
tabViewController.tabViewItems.forEach { (tabViewItem) in
let identity = (tabViewItem.identifier as? String) ?? ""
if (identifierTOImageMapping[identity] != nil) {

4
Clocker/Preferences/ParentViewController.swift

@ -8,8 +8,8 @@ class ParentViewController: NSViewController {
if let view = view as? ParentView {
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
class PermissionsViewController: ParentViewController {
@IBOutlet var calendarContainerView: NSView!
@IBOutlet var remindersContainerView: NSView!
@ -121,10 +121,10 @@ class PermissionsViewController: ParentViewController {
if granted {
OperationQueue.main.addOperation {
self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true)
self.calendarButton.title = "Granted"
// Used to update CalendarViewController's view
@ -155,10 +155,10 @@ class PermissionsViewController: ParentViewController {
if granted {
OperationQueue.main.addOperation {
self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true)
self.remindersButton.title = "Granted"
}
} 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