Browse Source

Swiftformatting.

pull/92/head
Abhishek Banthia 6 years ago
parent
commit
05505fc235
  1. 1
      .swiftformat
  2. 47
      Clocker/AppDelegate.swift
  3. 8
      Clocker/Clocker.xcodeproj/project.pbxproj
  4. 1
      Clocker/Clocker/LocationController.swift
  5. 2
      Clocker/ClockerUITests/FloatingWindowTests.swift
  6. 3
      Clocker/ClockerUITests/NetworkDisconnectionTests.swift
  7. 11
      Clocker/ClockerUITests/OnboardingTests.swift
  8. 9
      Clocker/ClockerUITests/PanelTests.swift
  9. 13
      Clocker/ClockerUITests/PreferencesTest.swift
  10. 3
      Clocker/ClockerUITests/ReviewTests.swift
  11. 11
      Clocker/ClockerUITests/ShortcutTests.swift
  12. 93
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  13. 4
      Clocker/ClockerUnitTests/RateTests.swift
  14. 16
      Clocker/Dependencies/Date Additions/Constants.swift
  15. 9
      Clocker/Dependencies/Date Additions/Date+Bundle.swift
  16. 93
      Clocker/Dependencies/Date Additions/Date+Comparators.swift
  17. 190
      Clocker/Dependencies/Date Additions/Date+Components.swift
  18. 1
      Clocker/Dependencies/Date Additions/Date+Format.swift
  19. 67
      Clocker/Dependencies/Date Additions/Date+Inits.swift
  20. 16
      Clocker/Dependencies/Date Additions/Date+Manipulations.swift
  21. 168
      Clocker/Dependencies/Date Additions/Date+TimeAgo.swift
  22. 14
      Clocker/Dependencies/Date Additions/Enums.swift
  23. 1
      Clocker/Dependencies/Date Additions/Integer+DateTools.swift
  24. 88
      Clocker/Dependencies/Date Additions/TimeChunk.swift
  25. 217
      Clocker/Dependencies/Date Additions/TimePeriod.swift
  26. 53
      Clocker/Dependencies/Date Additions/TimePeriodChain.swift
  27. 56
      Clocker/Dependencies/Date Additions/TimePeriodCollection.swift
  28. 33
      Clocker/Dependencies/Date Additions/TimePeriodGroup.swift
  29. 38
      Clocker/Dependencies/Solar.swift
  30. 17
      Clocker/Events and Reminders/CalendarHandler.swift
  31. 2
      Clocker/Events and Reminders/EventCenter.swift
  32. 5
      Clocker/Events and Reminders/RemindersHandler.swift
  33. 12
      Clocker/Menu Bar/MenubarHandler.swift
  34. 8
      Clocker/Menu Bar/StatusContainerView.swift
  35. 9
      Clocker/Menu Bar/StatusItemHandler.swift
  36. 23
      Clocker/Menu Bar/StatusItemView.swift
  37. 66
      Clocker/Onboarding/OnboardingParentViewController.swift
  38. 3
      Clocker/Onboarding/OnboardingPermissionsViewController.swift
  39. 94
      Clocker/Onboarding/OnboardingSearchController.swift
  40. 11
      Clocker/Overall App/AppDefaults.swift
  41. 6
      Clocker/Overall App/AppKit + Additions.swift
  42. 2
      Clocker/Overall App/DateFormatterManager.swift
  43. 2
      Clocker/Overall App/Reach.swift
  44. 5
      Clocker/Overall App/Themer.swift
  45. 4
      Clocker/Overall App/Timer.swift
  46. 1
      Clocker/Overall App/UserDefaults + KVOExtensions.swift
  47. 9
      Clocker/Panel/Data Layer/TimezoneData.swift
  48. 19
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  49. 2
      Clocker/Panel/FloatingWindowController.swift
  50. 8
      Clocker/Panel/Notes Popover/NotesPopover.swift
  51. 4
      Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift
  52. 13
      Clocker/Panel/PanelController.swift
  53. 42
      Clocker/Panel/ParentPanelController.swift
  54. 8
      Clocker/Panel/Rate Controller/RateController.swift
  55. 4
      Clocker/Panel/Rate Controller/ReviewView.swift
  56. 4
      Clocker/Panel/UI/CustomSliderCell.swift
  57. 12
      Clocker/Panel/UI/NoTimezoneView.swift
  58. 2
      Clocker/Panel/UI/PanelTableView.swift
  59. 2
      Clocker/Panel/UI/TimezoneCellView.swift
  60. 4
      Clocker/Panel/UI/TimezoneDataSource.swift
  61. 23
      Clocker/Preferences/About/AboutViewController.swift
  62. 2
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  63. 57
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  64. 70
      Clocker/Preferences/Calendar/CalendarViewController.swift
  65. 56
      Clocker/Preferences/General/PreferencesViewController.swift
  66. 20
      Clocker/Preferences/OneWindowController.swift
  67. 3
      Clocker/Preferences/Permissions/PermissionsViewController.swift

1
.swiftformat

@ -0,0 +1 @@
--disable

47
Clocker/AppDelegate.swift

@ -3,9 +3,8 @@
import Cocoa import Cocoa
open class AppDelegate: NSObject, NSApplicationDelegate { open class AppDelegate: NSObject, NSApplicationDelegate {
private lazy var floatingWindow: FloatingWindowController = FloatingWindowController.shared()
lazy private var floatingWindow: FloatingWindowController = FloatingWindowController.shared() private lazy var panelController: PanelController = PanelController.shared()
lazy private var panelController: PanelController = PanelController.shared()
private var statusBarHandler: StatusItemHandler! private var statusBarHandler: StatusItemHandler!
private var panelObserver: NSKeyValueObservation? private var panelObserver: NSKeyValueObservation?
@ -13,10 +12,8 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
panelObserver?.invalidate() panelObserver?.invalidate()
} }
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
if let path = keyPath, path == "values.globalPing" { if let path = keyPath, path == "values.globalPing" {
let hotKeyCenter = PTHotKeyCenter.shared() let hotKeyCenter = PTHotKeyCenter.shared()
// Unregister old hot key // Unregister old hot key
@ -36,16 +33,14 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
hotKeyCenter?.register(newHotKey) hotKeyCenter?.register(newHotKey)
} }
} }
public func applicationWillFinishLaunching(_ notification: Notification) { public func applicationWillFinishLaunching(_: Notification) {
iVersion.sharedInstance().useAllAvailableLanguages = true iVersion.sharedInstance().useAllAvailableLanguages = true
iVersion.sharedInstance().verboseLogging = false iVersion.sharedInstance().verboseLogging = false
} }
public func applicationDidFinishLaunching(_ notification: Notification) { public func applicationDidFinishLaunching(_: Notification) {
// Initializing the event store takes really long // Initializing the event store takes really long
EventCenter.sharedCenter() EventCenter.sharedCenter()
@ -64,7 +59,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
#endif #endif
} }
public func applicationDockMenu(_ sender: NSApplication) -> NSMenu? { public func applicationDockMenu(_: NSApplication) -> NSMenu? {
let menu = NSMenu(title: "Quick Access") let menu = NSMenu(title: "Quick Access")
Logger.log(object: ["Dock Menu Triggered": "YES"], for: "Dock Menu Triggered") Logger.log(object: ["Dock Menu Triggered": "YES"], for: "Dock Menu Triggered")
@ -93,13 +88,13 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
} }
private lazy var controller: OnboardingController? = { private lazy var controller: OnboardingController? = {
let onboardingStoryboard = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil) let onboardingStoryboard = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil)
return onboardingStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController return onboardingStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController
}() }()
private func showOnboardingFlow() { private func showOnboardingFlow() {
let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty) let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty)
|| (ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument)) || ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument)
shouldLaunchOnboarding ? controller?.launch() : continueUsually() shouldLaunchOnboarding ? controller?.launch() : continueUsually()
} }
@ -127,8 +122,8 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
assignShortcut() assignShortcut()
panelObserver = panelController.observe(\.hasActivePanel, options: [.new]) { (obj, _) in panelObserver = panelController.observe(\.hasActivePanel, options: [.new]) { obj, _ in
self.statusBarHandler.setHasActiveIcon(obj.hasActivePanelGetter()) self.statusBarHandler.setHasActiveIcon(obj.hasActivePanelGetter())
} }
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
@ -153,7 +148,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
private func checkIfAppIsAlreadyOpen() { private func checkIfAppIsAlreadyOpen() {
guard let bundleID = Bundle.main.bundleIdentifier else { guard let bundleID = Bundle.main.bundleIdentifier else {
return return
} }
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID)
@ -218,13 +213,12 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
private func assignShortcut() { private func assignShortcut() {
NSUserDefaultsController.shared.addObserver(self, NSUserDefaultsController.shared.addObserver(self,
forKeyPath: "values.globalPing", forKeyPath: "values.globalPing",
options: [.initial, .new], options: [.initial, .new],
context: nil) context: nil)
} }
private func checkIfRunFromApplicationsFolder() { private func checkIfRunFromApplicationsFolder() {
if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true { if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true {
return return
} }
@ -240,10 +234,10 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
} }
let informativeText = """ let informativeText = """
Clocker must be run from the Applications folder in order to work properly. Clocker must be run from the Applications folder in order to work properly.
Please quit Clocker, move it to the Applications folder, and relaunch. Please quit Clocker, move it to the Applications folder, and relaunch.
Current folder: \(applicationDirectory)" Current folder: \(applicationDirectory)"
""" """
// Clocker is installed out of Applications directory // Clocker is installed out of Applications directory
// This breaks start at login! Time to show an alert and terminate // This breaks start at login! Time to show an alert and terminate
@ -255,8 +249,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
NSApp.terminate(nil) NSApp.terminate(nil)
} }
@IBAction open func togglePanel(_ sender: Any) { @IBAction open func togglePanel(_: Any) {
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground) let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground)
if displayMode == 1 { if displayMode == 1 {

8
Clocker/Clocker.xcodeproj/project.pbxproj

@ -896,7 +896,7 @@
9A4379201BEC220200F4E27F /* ShellScript */, 9A4379201BEC220200F4E27F /* ShellScript */,
9A20A0711C4E808500FB45AB /* Login Item Helper */, 9A20A0711C4E808500FB45AB /* Login Item Helper */,
9A5E75EC204CC39700119939 /* Embed Frameworks */, 9A5E75EC204CC39700119939 /* Embed Frameworks */,
C2A632A020EAC5EE00EB6BEA /* Move .app to Applications */, C2A632A020EAC5EE00EB6BEA /* SwiftFormat */,
); );
buildRules = ( buildRules = (
); );
@ -1070,19 +1070,19 @@
shellScript = ""; shellScript = "";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
C2A632A020EAC5EE00EB6BEA /* Move .app to Applications */ = { C2A632A020EAC5EE00EB6BEA /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputPaths = (
); );
name = "Move .app to Applications"; name = SwiftFormat;
outputPaths = ( outputPaths = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "# sh /Users/abhishek_banthia/Documents/GitLab/Check/Clocker-Private/Clocker/Move.sh\n"; shellScript = "if which swiftformat >/dev/null; then\n swiftformat . --swiftversion 5\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n";
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */

1
Clocker/Clocker/LocationController.swift

@ -87,7 +87,6 @@ class LocationController: NSObject {
extension LocationController: CLLocationManagerDelegate { extension LocationController: CLLocationManagerDelegate {
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard !locations.isEmpty, let coordinates = locations.first?.coordinate else { return } guard !locations.isEmpty, let coordinates = locations.first?.coordinate else { return }
let reverseGeoCoder = CLGeocoder() let reverseGeoCoder = CLGeocoder()

2
Clocker/ClockerUITests/FloatingWindowTests.swift

@ -114,7 +114,7 @@ class FloatingWindowTests: XCTestCase {
let menubarDisplayQuery = app.tables.checkBoxes.matching(NSPredicate(format: "value == 1", "")) let menubarDisplayQuery = app.tables.checkBoxes.matching(NSPredicate(format: "value == 1", ""))
let menubarDisplayQueryCount = menubarDisplayQuery.count let menubarDisplayQueryCount = menubarDisplayQuery.count
for index in 0..<menubarDisplayQueryCount where index < menubarDisplayQueryCount { for index in 0 ..< menubarDisplayQueryCount where index < menubarDisplayQueryCount {
menubarDisplayQuery.element(boundBy: 0).click() menubarDisplayQuery.element(boundBy: 0).click()
sleep(1) sleep(1)
} }

3
Clocker/ClockerUITests/NetworkDisconnectionTests.swift

@ -3,7 +3,6 @@
import XCTest import XCTest
class NetworkDisconnectionTests: XCTestCase { class NetworkDisconnectionTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
@ -41,7 +40,6 @@ class NetworkDisconnectionTests: XCTestCase {
} }
func testFetchingATimezone() { func testFetchingATimezone() {
app.launchArguments.append("mockTimezoneDown") app.launchArguments.append("mockTimezoneDown")
precondition() precondition()
app.buttons["FloatingPreferences"].click() app.buttons["FloatingPreferences"].click()
@ -70,5 +68,4 @@ class NetworkDisconnectionTests: XCTestCase {
XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists) XCTAssertTrue(app.sheets.staticTexts["ErrorPlaceholder"].exists)
app.sheets.buttons["Close"].click() app.sheets.buttons["Close"].click()
} }
} }

11
Clocker/ClockerUITests/OnboardingTests.swift

@ -5,7 +5,6 @@ import XCTest
let CLOnboaringTestsLaunchArgument = "isTestingTheOnboardingFlow" let CLOnboaringTestsLaunchArgument = "isTestingTheOnboardingFlow"
class OnboardingTests: XCTestCase { class OnboardingTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
@ -19,7 +18,6 @@ class OnboardingTests: XCTestCase {
// 1. The flow (forward button and back button take the user to the correct screen) // 1. The flow (forward button and back button take the user to the correct screen)
// 2. Static texts and button title's are appropriate // 2. Static texts and button title's are appropriate
func testForwardButton() { func testForwardButton() {
welcomeControllerTests() welcomeControllerTests()
// Let's go to the Permissions View // Let's go to the Permissions View
@ -42,7 +40,6 @@ class OnboardingTests: XCTestCase {
} }
func backButtonTests() { func backButtonTests() {
moveBackward() moveBackward()
searchControllerTests() searchControllerTests()
@ -59,7 +56,6 @@ class OnboardingTests: XCTestCase {
} }
func alternateStartupFlowTests() { func alternateStartupFlowTests() {
// Let's go to the Permissions View // Let's go to the Permissions View
moveForward() moveForward()
permissionsControllerTests() permissionsControllerTests()
@ -114,7 +110,6 @@ class OnboardingTests: XCTestCase {
} }
private func permissionsControllerTests() { private func permissionsControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertTrue(onboardingWindow.staticTexts["Permissions"].exists, "Header label's static text was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.staticTexts["Permissions"].exists, "Header label's static text was unexpectedly wrong.")
@ -126,7 +121,6 @@ class OnboardingTests: XCTestCase {
} }
private func startupControllerTests() { private func startupControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Open Clocker At Login", "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Open Clocker At Login", "Forward button title's was unexpectedly wrong")
@ -137,8 +131,7 @@ class OnboardingTests: XCTestCase {
} }
private func searchControllerTests() { private func searchControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"]
let onboardingWindow = app.windows["OnboardingWindow"]
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Continue", "Forward button title's was unexpectedly wrong")
@ -148,7 +141,6 @@ class OnboardingTests: XCTestCase {
} }
private func finalOnboardingControllerTests() { private func finalOnboardingControllerTests() {
let onboardingWindow = app.windows["OnboardingWindow"] let onboardingWindow = app.windows["OnboardingWindow"]
// Let's test the buttons // Let's test the buttons
@ -158,5 +150,4 @@ class OnboardingTests: XCTestCase {
XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.") XCTAssertFalse(onboardingWindow.buttons["Alternate"].exists, "Alternate button was unexpectedly present.")
XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Launch Clocker", "Forward button's title was unexpectedly wrong.") XCTAssertTrue(onboardingWindow.buttons["Forward"].title == "Launch Clocker", "Forward button's title was unexpectedly wrong.")
} }
} }

9
Clocker/ClockerUITests/PanelTests.swift

@ -37,11 +37,10 @@ class PanelTests: XCTestCase {
} }
func testChangingLabelFromPopover() { func testChangingLabelFromPopover() {
app.tapMenubarIcon()
app.tapMenubarIcon() let cell = app.tables["mainTableView"].cells.firstMatch
let originalField = cell.staticTexts["CustomNameLabelForCell"]
let cell = app.tables["mainTableView"].cells.firstMatch
let originalField = cell.staticTexts["CustomNameLabelForCell"]
guard let originalValue = originalField.value as? String else { guard let originalValue = originalField.value as? String else {
XCTFail("Original Field's value was unexpectedly nil") XCTFail("Original Field's value was unexpectedly nil")
@ -109,7 +108,6 @@ class PanelTests: XCTestCase {
} }
func testRightMouseDownToShowPopover() { func testRightMouseDownToShowPopover() {
app.tapMenubarIcon() app.tapMenubarIcon()
let cell = app.tables["mainTableView"].cells.firstMatch let cell = app.tables["mainTableView"].cells.firstMatch
@ -117,5 +115,4 @@ class PanelTests: XCTestCase {
XCTAssert(app.popovers.count > 0) XCTAssert(app.popovers.count > 0)
} }
} }

13
Clocker/ClockerUITests/PreferencesTest.swift

@ -3,7 +3,6 @@
import XCTest import XCTest
class PreferencesTest: XCTestCase { class PreferencesTest: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
@ -56,7 +55,6 @@ class PreferencesTest: XCTestCase {
} }
func testEditingLabel() { func testEditingLabel() {
let placeToAdd = "Auckland" let placeToAdd = "Auckland"
app.tapMenubarIcon() app.tapMenubarIcon()
@ -83,7 +81,6 @@ class PreferencesTest: XCTestCase {
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
deleteAPlace(place: placeToAdd, for: app) deleteAPlace(place: placeToAdd, for: app)
} }
func testSortingByTimezoneDifference() { func testSortingByTimezoneDifference() {
@ -312,7 +309,7 @@ class PreferencesTest: XCTestCase {
let favouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 1", "")) let favouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 1", ""))
if favouritedMenubarsQuery.count > 1 { if favouritedMenubarsQuery.count > 1 {
for _ in 0..<favouritedMenubarsQuery.count { for _ in 0 ..< favouritedMenubarsQuery.count {
let checkbox = favouritedMenubarsQuery.element(boundBy: 0) let checkbox = favouritedMenubarsQuery.element(boundBy: 0)
checkbox.click() checkbox.click()
} }
@ -326,7 +323,7 @@ class PreferencesTest: XCTestCase {
let unfavouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 0", "")) let unfavouritedMenubarsQuery = preferencesTable.checkBoxes.matching(NSPredicate(format: "value == 0", ""))
if unfavouritedMenubarsQuery.count > 1 { if unfavouritedMenubarsQuery.count > 1 {
for _ in 0..<2 { for _ in 0 ..< 2 {
let checkbox = unfavouritedMenubarsQuery.element(boundBy: 0) let checkbox = unfavouritedMenubarsQuery.element(boundBy: 0)
checkbox.click() checkbox.click()
} }
@ -337,8 +334,8 @@ class PreferencesTest: XCTestCase {
let compactModeButton = app.dialogs.buttons["Enable Compact Mode"] let compactModeButton = app.dialogs.buttons["Enable Compact Mode"]
if compactModeButton.isHittable { if compactModeButton.isHittable {
compactModeButton.click() compactModeButton.click()
XCTAssertTrue(app.dialogs.count == 0) XCTAssertTrue(app.dialogs.count == 0)
} }
} }
@ -347,7 +344,6 @@ class PreferencesTest: XCTestCase {
let rowQueryCount = clockerWindow.tables["TimezoneTableView"].tableRows.count let rowQueryCount = clockerWindow.tables["TimezoneTableView"].tableRows.count
if rowQueryCount > 0 { if rowQueryCount > 0 {
let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch
currentElement.click() currentElement.click()
@ -355,7 +351,6 @@ class PreferencesTest: XCTestCase {
clockerWindow.typeKey(XCUIKeyboardKey.delete, clockerWindow.typeKey(XCUIKeyboardKey.delete,
modifierFlags: XCUIElement.KeyModifierFlags()) modifierFlags: XCUIElement.KeyModifierFlags())
} }
} }
} }
} }

3
Clocker/ClockerUITests/ReviewTests.swift

@ -3,7 +3,6 @@
import XCTest import XCTest
class ReviewTests: XCTestCase { class ReviewTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
override func setUp() { override func setUp() {
@ -19,7 +18,6 @@ class ReviewTests: XCTestCase {
} }
func testIfReviewIsNegativeAndUserWantsToProvideFeedback() { func testIfReviewIsNegativeAndUserWantsToProvideFeedback() {
guard app.buttons["Not Really"].exists else { return } guard app.buttons["Not Really"].exists else { return }
XCTAssertTrue(app.staticTexts["ReviewLabel"].exists) XCTAssertTrue(app.staticTexts["ReviewLabel"].exists)
app.buttons["Not Really"].click() app.buttons["Not Really"].click()
@ -55,5 +53,4 @@ class ReviewTests: XCTestCase {
app.buttons["Yes"].click() app.buttons["Yes"].click()
XCTAssertFalse(app.staticTexts["ReviewLabel"].exists) XCTAssertFalse(app.staticTexts["ReviewLabel"].exists)
} }
} }

11
Clocker/ClockerUITests/ShortcutTests.swift

@ -3,7 +3,6 @@
import XCTest import XCTest
class ShortcutTests: XCTestCase { class ShortcutTests: XCTestCase {
var app: XCUIApplication! var app: XCUIApplication!
let randomIndex = Int(arc4random_uniform(26)) let randomIndex = Int(arc4random_uniform(26))
@ -22,7 +21,6 @@ class ShortcutTests: XCTestCase {
} }
func testShortcuts() { func testShortcuts() {
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
XCTAssertFalse(app.tables["mainTableView"].exists) XCTAssertFalse(app.tables["mainTableView"].exists)
@ -45,16 +43,15 @@ class ShortcutTests: XCTestCase {
XCTAssertTrue(app.tables["mainTableView"].exists) XCTAssertTrue(app.tables["mainTableView"].exists)
// Reset the shortcut // Reset the shortcut
app.tables["mainTableView"].typeKey(",", modifierFlags: .command) app.tables["mainTableView"].typeKey(",", modifierFlags: .command)
app.windows["Clocker"].buttons["ShortcutControl"].click() app.windows["Clocker"].buttons["ShortcutControl"].click()
app.windows["Clocker"].typeKey(XCUIKeyboardKey.delete, modifierFlags: []) app.windows["Clocker"].typeKey(XCUIKeyboardKey.delete, modifierFlags: [])
app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command]) app.windows["Clocker"].typeKey(randomAlphabet, modifierFlags: [.shift, .command])
XCTAssertFalse(app.tables["mainTableView"].exists) XCTAssertFalse(app.tables["mainTableView"].exists)
} }
private func randomLetter() -> String { private func randomLetter() -> String {
let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] let alphabet: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
return alphabet[randomIndex] return alphabet[randomIndex]
} }
} }

93
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -1,55 +1,54 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker @testable import Clocker
import XCTest
class ClockerUnitTests: XCTestCase { class ClockerUnitTests: XCTestCase {
private let california = ["customLabel": "Test",
private let california = ["customLabel" : "Test", "formattedAddress": "San Francisco",
"formattedAddress" : "San Francisco", "place_id": "TestIdentifier",
"place_id" : "TestIdentifier", "timezoneID": "America/Los_Angeles",
"timezoneID" : "America/Los_Angeles", "nextUpdate": "",
"nextUpdate" : "", "latitude": "37.7749295",
"latitude" : "37.7749295", "longitude": "-122.4194155"]
"longitude" : "-122.4194155"]
private let mumbai = ["customLabel": "Ghar",
private let mumbai = ["customLabel" : "Ghar", "formattedAddress": "Mumbai",
"formattedAddress" : "Mumbai", "place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0",
"place_id" : "ChIJwe1EZjDG5zsRaYxkjY_tpF0", "timezoneID": "Asia/Calcutta",
"timezoneID" : "Asia/Calcutta", "nextUpdate": "",
"nextUpdate" : "", "latitude": "19.0759837",
"latitude" : "19.0759837", "longitude": "72.8776559"]
"longitude" : "72.8776559"]
private let auckland = ["customLabel": "Auckland",
private let auckland = ["customLabel" : "Auckland", "formattedAddress": "New Zealand",
"formattedAddress" : "New Zealand", "place_id": "ChIJh5Z3Fw4gLG0RM0dqdeIY1rE",
"place_id" : "ChIJh5Z3Fw4gLG0RM0dqdeIY1rE", "timezoneID": "Pacific/Auckland",
"timezoneID" : "Pacific/Auckland", "nextUpdate": "",
"nextUpdate" : "", "latitude": "-40.900557",
"latitude" : "-40.900557", "longitude": "174.885971"]
"longitude" : "174.885971"]
private let florida = ["customLabel": "Gainesville",
private let florida = ["customLabel" : "Gainesville", "formattedAddress": "Florida",
"formattedAddress" : "Florida", "place_id": "ChIJvypWkWV2wYgR0E7HW9MTLvc",
"place_id" : "ChIJvypWkWV2wYgR0E7HW9MTLvc", "timezoneID": "America/New_York",
"timezoneID" : "America/New_York", "nextUpdate": "",
"nextUpdate" : "", "latitude": "27.664827",
"latitude" : "27.664827", "longitude": "-81.5157535"]
"longitude" : "-81.5157535"]
private let onlyTimezone: [String: Any] = ["timezoneID": "Africa/Algiers", private let onlyTimezone: [String: Any] = ["timezoneID": "Africa/Algiers",
"formattedAddress" : "Africa/Algiers", "formattedAddress": "Africa/Algiers",
"place_id": "", "place_id": "",
"customLabel": "", "customLabel": "",
"latitude": "", "latitude": "",
"longitude": ""] "longitude": ""]
private let omaha: [String: Any] = ["timezoneID": "America/Chicago", private let omaha: [String: Any] = ["timezoneID": "America/Chicago",
"formattedAddress" : "Omaha", "formattedAddress": "Omaha",
"place_id": "ChIJ7fwMtciNk4cRBLY3rk9NQkY", "place_id": "ChIJ7fwMtciNk4cRBLY3rk9NQkY",
"customLabel": "", "customLabel": "",
"latitude": "41.2565369", "latitude": "41.2565369",
"longitude": "-95.9345034"] "longitude": "-95.9345034"]
private var operations: TimezoneDataOperations { private var operations: TimezoneDataOperations {
return TimezoneDataOperations(with: TimezoneData(with: mumbai)) return TimezoneDataOperations(with: TimezoneData(with: mumbai))
@ -72,7 +71,6 @@ class ClockerUnitTests: XCTestCase {
} }
func testAddingATimezoneToDefaults() { func testAddingATimezoneToDefaults() {
let timezoneData = TimezoneData(with: california) let timezoneData = TimezoneData(with: california)
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
@ -134,7 +132,6 @@ class ClockerUnitTests: XCTestCase {
} }
func testDateWithSliderValue() { func testDateWithSliderValue() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let operations = TimezoneDataOperations(with: dataObject) let operations = TimezoneDataOperations(with: dataObject)
@ -174,7 +171,6 @@ class ClockerUnitTests: XCTestCase {
} }
func testFormattedLabel() { func testFormattedLabel() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model.") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model.")
@ -200,7 +196,6 @@ class ClockerUnitTests: XCTestCase {
} }
func testWithAllLocales() { func testWithAllLocales() {
let dataObject1 = TimezoneData(with: mumbai) let dataObject1 = TimezoneData(with: mumbai)
let operations = TimezoneDataOperations(with: dataObject1) let operations = TimezoneDataOperations(with: dataObject1)
@ -212,17 +207,16 @@ class ClockerUnitTests: XCTestCase {
} }
func testTimeWithAllLocales() { func testTimeWithAllLocales() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let cal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) let cal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
guard let newDate = cal?.date(byAdding: .minute, guard let newDate = cal?.date(byAdding: .minute,
value: 0, value: 0,
to: Date(), to: Date(),
options: .matchFirst) else { options: .matchFirst) else {
XCTFail("Unable to add dates!") XCTFail("Unable to add dates!")
return return
} }
for locale in Locale.availableIdentifiers { for locale in Locale.availableIdentifiers {
@ -235,5 +229,4 @@ class ClockerUnitTests: XCTestCase {
XCTAssertNotNil(convertedDate) XCTAssertNotNil(convertedDate)
} }
} }
} }

4
Clocker/ClockerUnitTests/RateTests.swift

@ -1,10 +1,9 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker @testable import Clocker
import XCTest
class RateTests: XCTestCase { class RateTests: XCTestCase {
let rateController = RateController.applicationDidLaunch(UserDefaults()) let rateController = RateController.applicationDidLaunch(UserDefaults())
override func setUp() { override func setUp() {
@ -15,5 +14,4 @@ class RateTests: XCTestCase {
// This is an example of a functional test case. // This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results. // Use XCTAssert and related functions to verify your tests produce the correct results.
} }
} }

16
Clocker/Dependencies/Date Additions/Constants.swift

@ -12,17 +12,17 @@ import Foundation
* Time conversions used across DateTools * Time conversions used across DateTools
*/ */
public class Constants { public class Constants {
public static let SecondsInYear: TimeInterval = 31536000 public static let SecondsInYear: TimeInterval = 31_536_000
public static let SecondsInLeapYear: TimeInterval = 31622400 public static let SecondsInLeapYear: TimeInterval = 31_622_400
public static let SecondsInMonth28: TimeInterval = 2419200 public static let SecondsInMonth28: TimeInterval = 2_419_200
public static let SecondsInMonth29: TimeInterval = 2505600 public static let SecondsInMonth29: TimeInterval = 2_505_600
public static let SecondsInMonth30: TimeInterval = 2592000 public static let SecondsInMonth30: TimeInterval = 2_592_000
public static let SecondsInMonth31: TimeInterval = 2678400 public static let SecondsInMonth31: TimeInterval = 2_678_400
public static let SecondsInWeek: TimeInterval = 604800 public static let SecondsInWeek: TimeInterval = 604_800
public static let SecondsInDay: TimeInterval = 86400 public static let SecondsInDay: TimeInterval = 86400
public static let SecondsInHour: TimeInterval = 3600 public static let SecondsInHour: TimeInterval = 3600
public static let SecondsInMinute: TimeInterval = 60 public static let SecondsInMinute: TimeInterval = 60
public static let MillisecondsInDay: TimeInterval = 86400000 public static let MillisecondsInDay: TimeInterval = 86_400_000
public static let AllCalendarUnitFlags: Set<Calendar.Component> = [.year, .quarter, .month, .weekOfYear, .weekOfMonth, .day, .hour, .minute, .second, .era, .weekday, .weekdayOrdinal, .weekOfYear] public static let AllCalendarUnitFlags: Set<Calendar.Component> = [.year, .quarter, .month, .weekOfYear, .weekOfMonth, .day, .hour, .minute, .second, .era, .weekday, .weekdayOrdinal, .weekOfYear]
} }

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

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

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

@ -15,7 +15,6 @@ import Foundation
* for a given unit of time. * for a given unit of time.
*/ */
public extension Date { public extension Date {
// MARK: - Comparisons // MARK: - Comparisons
/** /**
@ -28,15 +27,15 @@ public extension Date {
* let age = birthday.chunkBetween(date: formatter.date(from: "2016 10 07 15:27:12.000")!) * let age = birthday.chunkBetween(date: formatter.date(from: "2016 10 07 15:27:12.000")!)
* ``` * ```
* *
* The age variable will have a chunk of time with year, month, day, hour, minute, * The age variable will have a chunk of time with year, month, day, hour, minute,
* and second components (note that we do not use weeks since they are not components * and second components (note that we do not use weeks since they are not components
* of `Calendar`). So if you just wanted the age in years, you could then say: age.years. * of `Calendar`). So if you just wanted the age in years, you could then say: age.years.
* *
* The chunk is calculated exactly as you'd say it in real life, always converting up * The chunk is calculated exactly as you'd say it in real life, always converting up
* when a lower unit equals 1 of the unit above it. The above example returns * when a lower unit equals 1 of the unit above it. The above example returns
* `TimeChunk(seconds: 0, minutes: 37, hours: 0, days: 13, weeks: 0, months: 10, years: 0)`. * `TimeChunk(seconds: 0, minutes: 37, hours: 0, days: 13, weeks: 0, months: 10, years: 0)`.
* *
* Passing a future date returns a TimeChunk with all positive components and passing * Passing a future date returns a TimeChunk with all positive components and passing
* a date in the past returns one with all negative components. * a date in the past returns one with all negative components.
* *
* - parameter date: The date of reference from the date called on * - parameter date: The date of reference from the date called on
@ -56,9 +55,9 @@ public extension Date {
* *
* - returns: Bool representing comparison result * - returns: Bool representing comparison result
*/ */
func equals(_ date: Date) -> Bool { func equals(_ date: Date) -> Bool {
return self.compare(date) == .orderedSame return compare(date) == .orderedSame
} }
/** /**
* Returns a true if receiver is later than provided comparison date, otherwise * Returns a true if receiver is later than provided comparison date, otherwise
@ -68,9 +67,9 @@ public extension Date {
* *
* - returns: Bool representing comparison result * - returns: Bool representing comparison result
*/ */
func isLater(than date: Date) -> Bool { func isLater(than date: Date) -> Bool {
return self.compare(date) == .orderedDescending return compare(date) == .orderedDescending
} }
/** /**
* Returns a true if receiver is later than or equal to provided comparison date, * Returns a true if receiver is later than or equal to provided comparison date,
@ -81,7 +80,7 @@ public extension Date {
* - returns: Bool representing comparison result * - returns: Bool representing comparison result
*/ */
func isLaterThanOrEqual(to date: Date) -> Bool { func isLaterThanOrEqual(to date: Date) -> Bool {
return self.compare(date) == .orderedDescending || self.compare(date) == .orderedSame return compare(date) == .orderedDescending || compare(date) == .orderedSame
} }
/** /**
@ -92,9 +91,9 @@ public extension Date {
* *
* - returns: Bool representing comparison result * - returns: Bool representing comparison result
*/ */
func isEarlier(than date: Date) -> Bool { func isEarlier(than date: Date) -> Bool {
return self.compare(date) == .orderedAscending return compare(date) == .orderedAscending
} }
/** /**
* Returns a true if receiver is earlier than or equal to the provided comparison date, * Returns a true if receiver is earlier than or equal to the provided comparison date,
@ -105,7 +104,7 @@ public extension Date {
* - returns: Bool representing comparison result * - returns: Bool representing comparison result
*/ */
func isEarlierThanOrEqual(to date: Date) -> Bool { func isEarlierThanOrEqual(to date: Date) -> Bool {
return self.compare(date) == .orderedAscending || self.compare(date) == .orderedSame return compare(date) == .orderedAscending || compare(date) == .orderedSame
} }
/** /**
@ -115,7 +114,7 @@ public extension Date {
* *
* - returns: True if both paramter dates fall on the same day, false otherwise * - returns: True if both paramter dates fall on the same day, false otherwise
*/ */
func isSameDay(date : Date ) -> Bool { func isSameDay(date: Date) -> Bool {
return Date.isSameDay(date: self, as: date) return Date.isSameDay(date: self, as: date)
} }
@ -154,7 +153,7 @@ public extension Date {
* - returns: The years between receiver and provided date * - returns: The years between receiver and provided date
*/ */
func years(from date: Date) -> Int { func years(from date: Date) -> Int {
return years(from: date, calendar:nil) return years(from: date, calendar: nil)
} }
/** /**
@ -169,7 +168,7 @@ public extension Date {
* - returns: The years between receiver and provided date * - returns: The years between receiver and provided date
*/ */
func months(from date: Date) -> Int { func months(from date: Date) -> Int {
return months(from: date, calendar:nil) return months(from: date, calendar: nil)
} }
/** /**
@ -184,7 +183,7 @@ public extension Date {
* - returns: The weeks between receiver and provided date * - returns: The weeks between receiver and provided date
*/ */
func weeks(from date: Date) -> Int { func weeks(from date: Date) -> Int {
return weeks(from: date, calendar:nil) return weeks(from: date, calendar: nil)
} }
/** /**
@ -199,7 +198,7 @@ public extension Date {
* - returns: The days between receiver and provided date * - returns: The days between receiver and provided date
*/ */
func days(from date: Date) -> Int { func days(from date: Date) -> Int {
return days(from: date, calendar:nil) return days(from: date, calendar: nil)
} }
/** /**
@ -213,7 +212,7 @@ public extension Date {
* - returns: The hours between receiver and provided date * - returns: The hours between receiver and provided date
*/ */
func hours(from date: Date) -> Int { func hours(from date: Date) -> Int {
return Int(self.timeIntervalSince(date)/Constants.SecondsInHour) return Int(timeIntervalSince(date) / Constants.SecondsInHour)
} }
/** /**
@ -227,7 +226,7 @@ public extension Date {
* - returns: The minutes between receiver and provided date * - returns: The minutes between receiver and provided date
*/ */
func minutes(from date: Date) -> Int { func minutes(from date: Date) -> Int {
return Int(self.timeIntervalSince(date)/Constants.SecondsInMinute) return Int(timeIntervalSince(date) / Constants.SecondsInMinute)
} }
/** /**
@ -259,7 +258,7 @@ public extension Date {
*/ */
func years(from date: Date, calendar: Calendar?) -> Int { func years(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if calendar == nil {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
@ -283,7 +282,7 @@ public extension Date {
*/ */
func months(from date: Date, calendar: Calendar?) -> Int { func months(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if calendar == nil {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
@ -291,7 +290,7 @@ public extension Date {
let latest = (earliest == self) ? date : self let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1 let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest) let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest)
return multiplier*(components.month! + 12*components.year!) return multiplier * (components.month! + 12 * components.year!)
} }
/** /**
@ -307,7 +306,7 @@ public extension Date {
*/ */
func weeks(from date: Date, calendar: Calendar?) -> Int { func weeks(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if calendar == nil {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
@ -315,7 +314,7 @@ public extension Date {
let latest = (earliest == self) ? date : self let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1 let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest) let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest)
return multiplier*components.weekOfYear! return multiplier * components.weekOfYear!
} }
/** /**
@ -331,7 +330,7 @@ public extension Date {
*/ */
func days(from date: Date, calendar: Calendar?) -> Int { func days(from date: Date, calendar: Calendar?) -> Int {
var calendarCopy = calendar var calendarCopy = calendar
if (calendar == nil) { if calendar == nil {
calendarCopy = Calendar.autoupdatingCurrent calendarCopy = Calendar.autoupdatingCurrent
} }
@ -339,13 +338,13 @@ public extension Date {
let latest = (earliest == self) ? date : self let latest = (earliest == self) ? date : self
let multiplier = (earliest == self) ? -1 : 1 let multiplier = (earliest == self) ? -1 : 1
let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest) let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest)
return multiplier*components.day! return multiplier * components.day!
} }
// MARK: Time Until // MARK: Time Until
/** /**
* The number of years until the receiver's date (0 if the receiver is the same or * The number of years until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var yearsUntil: Int { var yearsUntil: Int {
@ -353,7 +352,7 @@ public extension Date {
} }
/** /**
* The number of months until the receiver's date (0 if the receiver is the same or * The number of months until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var monthsUntil: Int { var monthsUntil: Int {
@ -361,7 +360,7 @@ public extension Date {
} }
/** /**
* The number of weeks until the receiver's date (0 if the receiver is the same or * The number of weeks until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var weeksUntil: Int { var weeksUntil: Int {
@ -369,7 +368,7 @@ public extension Date {
} }
/** /**
* The number of days until the receiver's date (0 if the receiver is the same or * The number of days until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var daysUntil: Int { var daysUntil: Int {
@ -377,7 +376,7 @@ public extension Date {
} }
/** /**
* The number of hours until the receiver's date (0 if the receiver is the same or * The number of hours until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var hoursUntil: Int { var hoursUntil: Int {
@ -385,7 +384,7 @@ public extension Date {
} }
/** /**
* The number of minutes until the receiver's date (0 if the receiver is the same or * The number of minutes until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var minutesUntil: Int { var minutesUntil: Int {
@ -393,7 +392,7 @@ public extension Date {
} }
/** /**
* The number of seconds until the receiver's date (0 if the receiver is the same or * The number of seconds until the receiver's date (0 if the receiver is the same or
* earlier than now). * earlier than now).
*/ */
var secondsUntil: Int { var secondsUntil: Int {
@ -403,7 +402,7 @@ public extension Date {
// MARK: Time Ago // MARK: Time Ago
/** /**
* The number of years the receiver's date is earlier than now (0 if the receiver is * The number of years the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var yearsAgo: Int { var yearsAgo: Int {
@ -411,7 +410,7 @@ public extension Date {
} }
/** /**
* The number of months the receiver's date is earlier than now (0 if the receiver is * The number of months the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var monthsAgo: Int { var monthsAgo: Int {
@ -419,7 +418,7 @@ public extension Date {
} }
/** /**
* The number of weeks the receiver's date is earlier than now (0 if the receiver is * The number of weeks the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var weeksAgo: Int { var weeksAgo: Int {
@ -427,7 +426,7 @@ public extension Date {
} }
/** /**
* The number of days the receiver's date is earlier than now (0 if the receiver is * The number of days the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var daysAgo: Int { var daysAgo: Int {
@ -435,7 +434,7 @@ public extension Date {
} }
/** /**
* The number of hours the receiver's date is earlier than now (0 if the receiver is * The number of hours the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var hoursAgo: Int { var hoursAgo: Int {
@ -443,7 +442,7 @@ public extension Date {
} }
/** /**
* The number of minutes the receiver's date is earlier than now (0 if the receiver is * The number of minutes the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var minutesAgo: Int { var minutesAgo: Int {
@ -451,7 +450,7 @@ public extension Date {
} }
/** /**
* The number of seconds the receiver's date is earlier than now (0 if the receiver is * The number of seconds the receiver's date is earlier than now (0 if the receiver is
* the same or earlier than now). * the same or earlier than now).
*/ */
var secondsAgo: Int { var secondsAgo: Int {
@ -548,7 +547,7 @@ public extension Date {
/** /**
* Returns the number of years the receiver's date is later than the provided * Returns the number of years the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
* comparison date. * comparison date.
* *
* - parameter date: Provided date for comparison * - parameter date: Provided date for comparison
@ -561,7 +560,7 @@ public extension Date {
/** /**
* Returns the number of months the receiver's date is later than the provided * Returns the number of months the receiver's date is later than the provided
* comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided
* comparison date. * comparison date.
* *
* - parameter date: Provided date for comparison * - parameter date: Provided date for comparison

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

@ -14,7 +14,6 @@ import Foundation
* several computed Bools. * several computed Bools.
*/ */
public extension Date { public extension Date {
/** /**
* Convenient accessor of the date's `Calendar` components. * Convenient accessor of the date's `Calendar` components.
* *
@ -24,9 +23,9 @@ public extension Date {
* *
*/ */
func component(_ component: Calendar.Component) -> Int { func component(_ component: Calendar.Component) -> Int {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.component(component, from: self) return calendar.component(component, from: self)
} }
/** /**
* Convenient accessor of the date's `Calendar` components ordinality. * Convenient accessor of the date's `Calendar` components ordinality.
@ -37,10 +36,10 @@ public extension Date {
* - returns: The ordinal number of a smaller calendar component within a specified larger calendar component * - returns: The ordinal number of a smaller calendar component within a specified larger calendar component
* *
*/ */
func ordinality(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? { func ordinality(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.ordinality(of: smaller, in: larger, for: self) return calendar.ordinality(of: smaller, in: larger, for: self)
} }
/** /**
* Use calendar components to determine how many units of a smaller component are inside 1 larger unit. * Use calendar components to determine how many units of a smaller component are inside 1 larger unit.
@ -54,17 +53,16 @@ public extension Date {
* - returns: The number of smaller units required to equal in 1 larger unit, given the date called on * - returns: The number of smaller units required to equal in 1 larger unit, given the date called on
* *
*/ */
func unit(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? { func unit(of smaller: Calendar.Component, in larger: Calendar.Component) -> Int? {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
var units = 1 var units = 1
var unitRange: Range<Int>? var unitRange: Range<Int>?
if larger.hashValue < smaller.hashValue { if larger.hashValue < smaller.hashValue {
for x in larger.hashValue..<smaller.hashValue { for x in larger.hashValue ..< smaller.hashValue {
var stepLarger: Calendar.Component var stepLarger: Calendar.Component
var stepSmaller: Calendar.Component var stepSmaller: Calendar.Component
switch(x) { switch x {
case 0: case 0:
stepLarger = Calendar.Component.era stepLarger = Calendar.Component.era
stepSmaller = Calendar.Component.year stepSmaller = Calendar.Component.year
@ -79,10 +77,10 @@ public extension Date {
} }
case 2: case 2:
if larger.hashValue < 2 { if larger.hashValue < 2 {
if self.isInLeapYear { if isInLeapYear {
unitRange = Range.init(uncheckedBounds: (lower: 0, upper: 366)) unitRange = Range(uncheckedBounds: (lower: 0, upper: 366))
} else { } else {
unitRange = Range.init(uncheckedBounds: (lower: 0, upper: 365)) unitRange = Range(uncheckedBounds: (lower: 0, upper: 365))
} }
} else { } else {
stepLarger = Calendar.Component.month stepLarger = Calendar.Component.month
@ -112,107 +110,107 @@ public extension Date {
return units return units
} }
return nil return nil
} }
// MARK: - Components // MARK: - Components
/** /**
* Convenience getter for the date's `era` component * Convenience getter for the date's `era` component
*/ */
var era: Int { var era: Int {
return component(.era) return component(.era)
} }
/** /**
* Convenience getter for the date's `year` component * Convenience getter for the date's `year` component
*/ */
var year: Int { var year: Int {
return component(.year) return component(.year)
} }
/** /**
* Convenience getter for the date's `month` component * Convenience getter for the date's `month` component
*/ */
var month: Int { var month: Int {
return component(.month) return component(.month)
} }
/** /**
* Convenience getter for the date's `week` component * Convenience getter for the date's `week` component
*/ */
var week: Int { var week: Int {
return component(.weekday) return component(.weekday)
} }
/** /**
* Convenience getter for the date's `day` component * Convenience getter for the date's `day` component
*/ */
var day: Int { var day: Int {
return component(.day) return component(.day)
} }
/** /**
* Convenience getter for the date's `hour` component * Convenience getter for the date's `hour` component
*/ */
var hour: Int { var hour: Int {
return component(.hour) return component(.hour)
} }
/** /**
* Convenience getter for the date's `minute` component * Convenience getter for the date's `minute` component
*/ */
var minute: Int { var minute: Int {
return component(.minute) return component(.minute)
} }
/** /**
* Convenience getter for the date's `second` component * Convenience getter for the date's `second` component
*/ */
var second: Int { var second: Int {
return component(.second) return component(.second)
} }
/** /**
* Convenience getter for the date's `weekday` component * Convenience getter for the date's `weekday` component
*/ */
var weekday: Int { var weekday: Int {
return component(.weekday) return component(.weekday)
} }
/** /**
* Convenience getter for the date's `weekdayOrdinal` component * Convenience getter for the date's `weekdayOrdinal` component
*/ */
var weekdayOrdinal: Int { var weekdayOrdinal: Int {
return component(.weekdayOrdinal) return component(.weekdayOrdinal)
} }
/** /**
* Convenience getter for the date's `quarter` component * Convenience getter for the date's `quarter` component
*/ */
var quarter: Int { var quarter: Int {
return component(.quarter) return component(.quarter)
} }
/** /**
* Convenience getter for the date's `weekOfYear` component * Convenience getter for the date's `weekOfYear` component
*/ */
var weekOfMonth: Int { var weekOfMonth: Int {
return component(.weekOfMonth) return component(.weekOfMonth)
} }
/** /**
* Convenience getter for the date's `weekOfYear` component * Convenience getter for the date's `weekOfYear` component
*/ */
var weekOfYear: Int { var weekOfYear: Int {
return component(.weekOfYear) return component(.weekOfYear)
} }
/** /**
* Convenience getter for the date's `yearForWeekOfYear` component * Convenience getter for the date's `yearForWeekOfYear` component
*/ */
var yearForWeekOfYear: Int { var yearForWeekOfYear: Int {
return component(.yearForWeekOfYear) return component(.yearForWeekOfYear)
} }
/** /**
* Convenience getter for the date's `daysInMonth` component * Convenience getter for the date's `daysInMonth` component
@ -229,42 +227,42 @@ public extension Date {
* Convenience setter for the date's `year` component * Convenience setter for the date's `year` component
*/ */
mutating func year(_ year: Int) { mutating func year(_ year: Int) {
self = Date.init(year: year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: self.second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
/** /**
* Convenience setter for the date's `month` component * Convenience setter for the date's `month` component
*/ */
mutating func month(_ month: Int) { mutating func month(_ month: Int) {
self = Date.init(year: self.year, month: month, day: self.day, hour: self.hour, minute: self.minute, second: self.second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
/** /**
* Convenience setter for the date's `day` component * Convenience setter for the date's `day` component
*/ */
mutating func day(_ day: Int) { mutating func day(_ day: Int) {
self = Date.init(year: self.year, month: self.month, day: day, hour: self.hour, minute: self.minute, second: self.second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
/** /**
* Convenience setter for the date's `hour` component * Convenience setter for the date's `hour` component
*/ */
mutating func hour(_ hour: Int) { mutating func hour(_ hour: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: hour, minute: self.minute, second: self.second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
/** /**
* Convenience setter for the date's `minute` component * Convenience setter for the date's `minute` component
*/ */
mutating func minute(_ minute: Int) { mutating func minute(_ minute: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: minute, second: self.second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
/** /**
* Convenience setter for the date's `second` component * Convenience setter for the date's `second` component
*/ */
mutating func second(_ second: Int) { mutating func second(_ second: Int) {
self = Date.init(year: self.year, month: self.month, day: self.day, hour: self.hour, minute: self.minute, second: second) self = Date(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
} }
// MARK: - Bools // MARK: - Bools
@ -272,52 +270,52 @@ public extension Date {
/** /**
* Determine if date is in a leap year * Determine if date is in a leap year
*/ */
var isInLeapYear: Bool { var isInLeapYear: Bool {
let yearComponent = component(.year) let yearComponent = component(.year)
if yearComponent % 400 == 0 { if yearComponent % 400 == 0 {
return true return true
} }
if yearComponent % 100 == 0 { if yearComponent % 100 == 0 {
return false return false
} }
if yearComponent % 4 == 0 { if yearComponent % 4 == 0 {
return true return true
} }
return false return false
} }
/** /**
* Determine if date is within the current day * Determine if date is within the current day
*/ */
var isToday: Bool { var isToday: Bool {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInToday(self) return calendar.isDateInToday(self)
} }
/** /**
* Determine if date is within the day tomorrow * Determine if date is within the day tomorrow
*/ */
var isTomorrow: Bool { var isTomorrow: Bool {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInTomorrow(self) return calendar.isDateInTomorrow(self)
} }
/** /**
* Determine if date is within yesterday * Determine if date is within yesterday
*/ */
var isYesterday: Bool { var isYesterday: Bool {
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
return calendar.isDateInYesterday(self) return calendar.isDateInYesterday(self)
} }
/** /**
* Determine if date is in a weekend * Determine if date is in a weekend
*/ */
var isWeekend: Bool { var isWeekend: Bool {
if weekday == 7 || weekday == 1 { if weekday == 7 || weekday == 1 {
return true return true
} }
return false return false
} }
} }

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

@ -12,7 +12,6 @@ import Foundation
* Extends the Date class by adding convenience methods for formatting dates. * Extends the Date class by adding convenience methods for formatting dates.
*/ */
public extension Date { public extension Date {
// MARK: - Formatted Date - Style // MARK: - Formatted Date - Style
/** /**

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

@ -14,8 +14,7 @@ import Foundation
*/ */
public extension Date { public extension Date {
// MARK: - Initializers
// MARK: - Initializers
/** /**
* Init date with components. * Init date with components.
@ -27,21 +26,21 @@ public extension Date {
* - parameter minute: Minute component of new date * - parameter minute: Minute component of new date
* - parameter second: Second component of new date * - parameter second: Second component of new date
*/ */
init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) { init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) {
var dateComponents = DateComponents() var dateComponents = DateComponents()
dateComponents.year = year dateComponents.year = year
dateComponents.month = month dateComponents.month = month
dateComponents.day = day dateComponents.day = day
dateComponents.hour = hour dateComponents.hour = hour
dateComponents.minute = minute dateComponents.minute = minute
dateComponents.second = second dateComponents.second = second
guard let date = Calendar.current.date(from: dateComponents) else { guard let date = Calendar.current.date(from: dateComponents) else {
self = Date() self = Date()
return return
} }
self = date self = date
} }
/** /**
* Init date with components. Hour, minutes, and seconds set to zero. * Init date with components. Hour, minutes, and seconds set to zero.
@ -50,9 +49,9 @@ public extension Date {
* - parameter month: Month component of new date * - parameter month: Month component of new date
* - parameter day: Day component of new date * - parameter day: Day component of new date
*/ */
init(year: Int, month: Int, day: Int) { init(year: Int, month: Int, day: Int) {
self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0) self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0)
} }
/** /**
* Init date from string, given a format string, according to Apple's date formatting guide, and time zone. * Init date from string, given a format string, according to Apple's date formatting guide, and time zone.
@ -61,19 +60,19 @@ public extension Date {
* - parameter format: Format style using Apple's date formatting guide * - parameter format: Format style using Apple's date formatting guide
* - parameter timeZone: Time zone of date * - parameter timeZone: Time zone of date
*/ */
init(dateString: String, format: String, timeZone: TimeZone) { init(dateString: String, format: String, timeZone: TimeZone) {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none dateFormatter.timeStyle = .none
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
dateFormatter.dateFormat = format dateFormatter.dateFormat = format
guard let date = dateFormatter.date(from: dateString) else { guard let date = dateFormatter.date(from: dateString) else {
self = Date() self = Date()
return return
} }
self = date self = date
} }
/** /**
* Init date from string, given a format string, according to Apple's date formatting guide. * Init date from string, given a format string, according to Apple's date formatting guide.
@ -82,7 +81,7 @@ public extension Date {
* - parameter dateString: Date in the formatting given by the format parameter * - parameter dateString: Date in the formatting given by the format parameter
* - parameter format: Format style using Apple's date formatting guide * - parameter format: Format style using Apple's date formatting guide
*/ */
init (dateString: String, format: String) { init(dateString: String, format: String) {
self.init(dateString: dateString, format: format, timeZone: TimeZone.autoupdatingCurrent) self.init(dateString: dateString, format: format, timeZone: TimeZone.autoupdatingCurrent)
} }
} }

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

@ -12,7 +12,6 @@ import Foundation
* Extends the Date class by adding manipulation methods for transforming dates * Extends the Date class by adding manipulation methods for transforming dates
*/ */
public extension Date { public extension Date {
// MARK: - StartOf // MARK: - StartOf
/** /**
@ -26,7 +25,7 @@ public extension Date {
func start(of component: Component) -> Date { func start(of component: Component) -> Date {
var newDate = self var newDate = self
if component == .second { if component == .second {
newDate.second(self.second) newDate.second(second)
} else if component == .minute { } else if component == .minute {
newDate.second(0) newDate.second(0)
} else if component == .hour { } else if component == .hour {
@ -99,10 +98,10 @@ public extension Date {
if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 { if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 {
// 31 day month // 31 day month
return 31 return 31
} else if month == 2 && date.isInLeapYear { } else if month == 2, date.isInLeapYear {
// February with leap year // February with leap year
return 29 return 29
} else if month == 2 && !date.isInLeapYear { } else if month == 2, !date.isInLeapYear {
// February without leap year // February without leap year
return 28 return 28
} else { } else {
@ -127,7 +126,7 @@ public extension Date {
var components = DateComponents() var components = DateComponents()
components.year = chunk.years components.year = chunk.years
components.month = chunk.months components.month = chunk.months
components.day = chunk.days + (chunk.weeks*7) components.day = chunk.days + (chunk.weeks * 7)
components.hour = chunk.hours components.hour = chunk.hours
components.minute = chunk.minutes components.minute = chunk.minutes
components.second = chunk.seconds components.second = chunk.seconds
@ -148,7 +147,7 @@ public extension Date {
var components = DateComponents() var components = DateComponents()
components.year = -chunk.years components.year = -chunk.years
components.month = -chunk.months components.month = -chunk.months
components.day = -(chunk.days + (chunk.weeks*7)) components.day = -(chunk.days + (chunk.weeks * 7))
components.hour = -chunk.hours components.hour = -chunk.hours
components.minute = -chunk.minutes components.minute = -chunk.minutes
components.second = -chunk.seconds components.second = -chunk.seconds
@ -175,14 +174,13 @@ public extension Date {
* Operator overload for adding a `TimeInterval` to a date. * Operator overload for adding a `TimeInterval` to a date.
*/ */
static func + (leftAddend: Date, rightAddend: Int) -> Date { static func + (leftAddend: Date, rightAddend: Int) -> Date {
return leftAddend.addingTimeInterval((TimeInterval(rightAddend))) return leftAddend.addingTimeInterval(TimeInterval(rightAddend))
} }
/** /**
* Operator overload for subtracting a `TimeInterval` from a date. * Operator overload for subtracting a `TimeInterval` from a date.
*/ */
static func - (minuend: Date, subtrahend: Int) -> Date { static func - (minuend: Date, subtrahend: Int) -> Date {
return minuend.addingTimeInterval(-(TimeInterval(subtrahend))) return minuend.addingTimeInterval(-TimeInterval(subtrahend))
} }
} }

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

@ -13,7 +13,6 @@ import Foundation
* time in String format. * time in String format.
*/ */
public extension Date { public extension Date {
// MARK: - Time Ago // MARK: - Time Ago
/** /**
@ -24,7 +23,7 @@ public extension Date {
* *
* - returns String - Formatted return string * - returns String - Formatted return string
*/ */
static func timeAgo(since date:Date) -> String { static func timeAgo(since date: Date) -> String {
return date.timeAgo(since: Date(), numericDates: false, numericTimes: false) return date.timeAgo(since: Date(), numericDates: false, numericTimes: false)
} }
@ -36,8 +35,8 @@ public extension Date {
* *
* - returns String - Formatted return string * - returns String - Formatted return string
*/ */
static func shortTimeAgo(since date:Date) -> String { static func shortTimeAgo(since date: Date) -> String {
return date.shortTimeAgo(since:Date()) return date.shortTimeAgo(since: Date())
} }
/** /**
@ -47,7 +46,7 @@ public extension Date {
* - returns String - Formatted return string * - returns String - Formatted return string
*/ */
var timeAgoSinceNow: String { var timeAgoSinceNow: String {
return self.timeAgo(since:Date()) return timeAgo(since: Date())
} }
/** /**
@ -57,82 +56,76 @@ public extension Date {
* - returns String - Formatted return string * - returns String - Formatted return string
*/ */
var shortTimeAgoSinceNow: String { var shortTimeAgoSinceNow: String {
return self.shortTimeAgo(since:Date()) return shortTimeAgo(since: Date())
} }
func timeAgo(since date:Date, numericDates: Bool = false, numericTimes: Bool = false) -> String { func timeAgo(since date: Date, numericDates: Bool = false, numericTimes: Bool = false) -> String {
let calendar = NSCalendar.current let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year]) let unitFlags = Set<Calendar.Component>([.second, .minute, .hour, .day, .weekOfYear, .month, .year])
let earliest = self.earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self //Should be triple equals, but not extended to Date at this time let latest = (earliest == self) ? date : self // Should be triple equals, but not extended to Date at this time
let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let components = calendar.dateComponents(unitFlags, from: earliest, to: latest)
let yesterday = date.subtract(1.days) let yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day let isYesterday = yesterday.day == 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) { // Not Yet Implemented/Optional
return self.logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!) // The following strings are present in the translation files but lack logic as of 2014.04.05
} else if (components.year! >= 1) { // @"Today", @"This week", @"This month", @"This year"
// and @"This morning", @"This afternoon"
if (numericDates) { if components.year! >= 2 {
return logicalLocalizedStringFromFormat(format: "%%d %@years ago", value: components.year!)
} else if components.year! >= 1 {
if numericDates {
return DateToolsLocalizedStrings("1 year ago") return DateToolsLocalizedStrings("1 year ago")
} }
return DateToolsLocalizedStrings("Last year") return DateToolsLocalizedStrings("Last year")
} else if (components.month! >= 2) { } else if components.month! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!) return logicalLocalizedStringFromFormat(format: "%%d %@months ago", value: components.month!)
} else if (components.month! >= 1) { } else if components.month! >= 1 {
if numericDates {
if (numericDates) {
return DateToolsLocalizedStrings("1 month ago") return DateToolsLocalizedStrings("1 month ago")
} }
return DateToolsLocalizedStrings("Last month") return DateToolsLocalizedStrings("Last month")
} else if (components.weekOfYear! >= 2) { } else if components.weekOfYear! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!) return logicalLocalizedStringFromFormat(format: "%%d %@weeks ago", value: components.weekOfYear!)
} else if (components.weekOfYear! >= 1) { } else if components.weekOfYear! >= 1 {
if numericDates {
if (numericDates) {
return DateToolsLocalizedStrings("1 week ago") return DateToolsLocalizedStrings("1 week ago")
} }
return DateToolsLocalizedStrings("Last week") return DateToolsLocalizedStrings("Last week")
} else if (components.day! >= 2) { } else if components.day! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!) return logicalLocalizedStringFromFormat(format: "%%d %@days ago", value: components.day!)
} else if (isYesterday) { } else if isYesterday {
if (numericDates) { if numericDates {
return DateToolsLocalizedStrings("1 day ago") return DateToolsLocalizedStrings("1 day ago")
} }
return DateToolsLocalizedStrings("Yesterday") return DateToolsLocalizedStrings("Yesterday")
} else if (components.hour! >= 2) { } else if components.hour! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!) return logicalLocalizedStringFromFormat(format: "%%d %@hours ago", value: components.hour!)
} else if (components.hour! >= 1) { } else if components.hour! >= 1 {
if numericTimes {
if (numericTimes) {
return DateToolsLocalizedStrings("1 hour ago") return DateToolsLocalizedStrings("1 hour ago")
} }
return DateToolsLocalizedStrings("An hour ago") return DateToolsLocalizedStrings("An hour ago")
} else if (components.minute! >= 2) { } else if components.minute! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@minutes ago", value: components.minute!) return logicalLocalizedStringFromFormat(format: "%%d %@minutes ago", value: components.minute!)
} else if (components.minute! >= 1) { } else if components.minute! >= 1 {
if numericTimes {
if (numericTimes) {
return DateToolsLocalizedStrings("1 minute ago") return DateToolsLocalizedStrings("1 minute ago")
} }
return DateToolsLocalizedStrings("A minute ago") return DateToolsLocalizedStrings("A minute ago")
} else if (components.second! >= 3) { } else if components.second! >= 3 {
return self.logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!) return logicalLocalizedStringFromFormat(format: "%%d %@seconds ago", value: components.second!)
} else { } else {
if numericTimes {
if (numericTimes) {
return DateToolsLocalizedStrings("1 second ago") return DateToolsLocalizedStrings("1 second ago")
} }
@ -140,58 +133,58 @@ public extension Date {
} }
} }
func shortTimeAgo(since date:Date) -> String { func shortTimeAgo(since date: Date) -> String {
let calendar = NSCalendar.current let calendar = NSCalendar.current
let unitFlags = Set<Calendar.Component>([.second,.minute,.hour,.day,.weekOfYear,.month,.year]) let unitFlags = Set<Calendar.Component>([.second, .minute, .hour, .day, .weekOfYear, .month, .year])
let earliest = self.earlierDate(date) let earliest = earlierDate(date)
let latest = (earliest == self) ? date : self //Should pbe triple equals, but not extended to Date at this time let latest = (earliest == self) ? date : self // Should pbe triple equals, but not extended to Date at this time
let components = calendar.dateComponents(unitFlags, from: earliest, to: latest) let components = calendar.dateComponents(unitFlags, from: earliest, to: latest)
let yesterday = date.subtract(1.days) let yesterday = date.subtract(1.days)
let isYesterday = yesterday.day == self.day let isYesterday = yesterday.day == day
if (components.year! >= 1) { if components.year! >= 1 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@y", value: components.year!) return logicalLocalizedStringFromFormat(format: "%%d%@y", value: components.year!)
} else if (components.month! >= 1) { } else if components.month! >= 1 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@M", value: components.month!) return logicalLocalizedStringFromFormat(format: "%%d%@M", value: components.month!)
} else if (components.weekOfYear! >= 1) { } else if components.weekOfYear! >= 1 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@w", value: components.weekOfYear!) return logicalLocalizedStringFromFormat(format: "%%d%@w", value: components.weekOfYear!)
} else if (components.day! >= 2) { } else if components.day! >= 2 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: components.day!) return logicalLocalizedStringFromFormat(format: "%%d%@d", value: components.day!)
} else if (isYesterday) { } else if isYesterday {
return self.logicalLocalizedStringFromFormat(format: "%%d%@d", value: 1) return logicalLocalizedStringFromFormat(format: "%%d%@d", value: 1)
} else if (components.hour! >= 1) { } else if components.hour! >= 1 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@h", value: components.hour!) return logicalLocalizedStringFromFormat(format: "%%d%@h", value: components.hour!)
} else if (components.minute! >= 1) { } else if components.minute! >= 1 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@m", value: components.minute!) return logicalLocalizedStringFromFormat(format: "%%d%@m", value: components.minute!)
} else if (components.second! >= 3) { } else if components.second! >= 3 {
return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) return logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!)
} else { } else {
return self.logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!) return logicalLocalizedStringFromFormat(format: "%%d%@s", value: components.second!)
//return DateToolsLocalizedStrings(@"Now"); //string not yet translated 2014.04.05 // return DateToolsLocalizedStrings(@"Now"); //string not yet translated 2014.04.05
} }
} }
private func logicalLocalizedStringFromFormat(format: String, value: Int) -> String { private func logicalLocalizedStringFromFormat(format: String, value: Int) -> String {
let localeFormat = String.init(format: format, getLocaleFormatUnderscoresWithValue(Double(value))) let localeFormat = String(format: format, getLocaleFormatUnderscoresWithValue(Double(value)))
return String.init(format: DateToolsLocalizedStrings(localeFormat), value) return String(format: DateToolsLocalizedStrings(localeFormat), value)
} }
private func getLocaleFormatUnderscoresWithValue(_ value: Double) -> String { private func getLocaleFormatUnderscoresWithValue(_ value: Double) -> String {
let localCode = Bundle.main.preferredLocalizations[0] let localCode = Bundle.main.preferredLocalizations[0]
if (localCode == "ru" || localCode == "uk") { if localCode == "ru" || localCode == "uk" {
let XY = Int(floor(value).truncatingRemainder(dividingBy: 100)) let XY = Int(floor(value).truncatingRemainder(dividingBy: 100))
let Y = Int(floor(value).truncatingRemainder(dividingBy: 10)) let Y = Int(floor(value).truncatingRemainder(dividingBy: 10))
if(Y == 0 || Y > 4 || (XY > 10 && XY < 15)) { if Y == 0 || Y > 4 || (XY > 10 && XY < 15) {
return "" return ""
} }
if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) { if Y > 1, Y < 5, XY < 10 || XY > 20 {
return "_" return "_"
} }
if(Y == 1 && XY != 11) { if Y == 1, XY != 11 {
return "__" return "__"
} }
} }
@ -202,12 +195,12 @@ public extension Date {
// MARK: - Localization // MARK: - Localization
private func DateToolsLocalizedStrings(_ string: String) -> String { private func DateToolsLocalizedStrings(_ string: String) -> String {
//let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle") // let classBundle = Bundle(for:TimeChunk.self as! AnyClass.Type).resourcePath!.appending("DateTools.bundle")
//let bundelPath = Bundle(path:classBundle)! // let bundelPath = Bundle(path:classBundle)!
#if os(Linux) #if os(Linux)
// NSLocalizedString() is not available yet, see: https://github.com/apple/swift-corelibs-foundation/blob/16f83ddcd311b768e30a93637af161676b0a5f2f/Foundation/NSData.swift // NSLocalizedString() is not available yet, see: https://github.com/apple/swift-corelibs-foundation/blob/16f83ddcd311b768e30a93637af161676b0a5f2f/Foundation/NSData.swift
// However, a seemingly-equivalent method from NSBundle is: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSBundle.swift // However, a seemingly-equivalent method from NSBundle is: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSBundle.swift
return Bundle.main.localizedString(forKey: string, value: "", table: "DateTools") return Bundle.main.localizedString(forKey: string, value: "", table: "DateTools")
#else #else
return NSLocalizedString(string, tableName: "DateTools", bundle: Bundle.dateToolsBundle(), value: "", comment: "") return NSLocalizedString(string, tableName: "DateTools", bundle: Bundle.dateToolsBundle(), value: "", comment: "")
@ -218,13 +211,13 @@ public extension Date {
/** /**
* Return the earlier of two dates, between self and a given date. * Return the earlier of two dates, between self and a given date.
* *
* - parameter date: The date to compare to self * - parameter date: The date to compare to self
* *
* - returns: The date that is earlier * - returns: The date that is earlier
*/ */
func earlierDate(_ date:Date) -> Date { func earlierDate(_ date: Date) -> Date {
return (self.timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : date return (timeIntervalSince1970 <= date.timeIntervalSince1970) ? self : date
} }
/** /**
@ -234,8 +227,7 @@ public extension Date {
* *
* - returns: The date that is later * - returns: The date that is later
*/ */
func laterDate(_ date:Date) -> Date { func laterDate(_ date: Date) -> Date {
return (self.timeIntervalSince1970 >= date.timeIntervalSince1970) ? self : date return (timeIntervalSince1970 >= date.timeIntervalSince1970) ? self : date
} }
} }

14
Clocker/Dependencies/Date Additions/Enums.swift

@ -9,12 +9,12 @@
// MARK: - Enums // MARK: - Enums
/** /**
* There may come a need, say when you are making a scheduling app, when * There may come a need, say when you are making a scheduling app, when
* it might be good to know how two time periods relate to one another. * it might be good to know how two time periods relate to one another.
* Are they the same? Is one inside of another? All these questions may be * Are they the same? Is one inside of another? All these questions may be
* asked using the relationship methods of DTTimePeriod. * asked using the relationship methods of DTTimePeriod.
* *
* Further reading: [GitHub](https://github.com/MatthewYork/DateTools#relationships), * Further reading: [GitHub](https://github.com/MatthewYork/DateTools#relationships),
* [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET) * [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET)
*/ */
public enum Relation { public enum Relation {
@ -48,7 +48,7 @@ public enum Interval {
/** /**
* When a time periods is lengthened or shortened, it does so anchoring one date * When a time periods is lengthened or shortened, it does so anchoring one date
* of the time period and then changing the other one. There is also an option to * of the time period and then changing the other one. There is also an option to
* anchor the centerpoint of the time period, changing both the start and end dates. * anchor the centerpoint of the time period, changing both the start and end dates.
*/ */
public enum Anchor { public enum Anchor {
@ -58,8 +58,8 @@ public enum Anchor {
} }
/** /**
* When a time periods is lengthened or shortened, it does so anchoring one date * When a time periods is lengthened or shortened, it does so anchoring one date
* of the time period and then changing the other one. There is also an option to * of the time period and then changing the other one. There is also an option to
* anchor the centerpoint of the time period, changing both the start and end dates. * anchor the centerpoint of the time period, changing both the start and end dates.
*/ */
public enum Component { public enum Component {

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

@ -9,7 +9,6 @@
import Foundation import Foundation
public extension Int { public extension Int {
// MARK: TimePeriod // MARK: TimePeriod
/** /**

88
Clocker/Dependencies/Date Additions/TimeChunk.swift

@ -11,7 +11,7 @@ import Foundation
/** /**
* TimeChunk represents an abstract collection of `DateComponent`s intended for use in date manipulation. A `TimeChunk` differs from a * TimeChunk represents an abstract collection of `DateComponent`s intended for use in date manipulation. A `TimeChunk` differs from a
* TimeInterval in that the former depends on the `Calendar` class (and takes into account daylight savings, leap year, etc.) while the * TimeInterval in that the former depends on the `Calendar` class (and takes into account daylight savings, leap year, etc.) while the
* latter depends on hard, second based adjustments that are independent from calendar constructs. * latter depends on hard, second based adjustments that are independent from calendar constructs.
* *
* In essence, TimeChunk is meant to be a thin, more flexible layer over the `Calender` and `DateComponent` classes for ease of use. * In essence, TimeChunk is meant to be a thin, more flexible layer over the `Calender` and `DateComponent` classes for ease of use.
* `TimeChunk`s may be created either by calling the provided initializer or shorthand like `2.days`. TimeChunks are manipulable and combine * `TimeChunk`s may be created either by calling the provided initializer or shorthand like `2.days`. TimeChunks are manipulable and combine
@ -20,7 +20,6 @@ import Foundation
* For more information about the utility of TimeChunks in relation to Dates, see the `Date+Manipulations` class. * For more information about the utility of TimeChunks in relation to Dates, see the `Date+Manipulations` class.
*/ */
public struct TimeChunk { public struct TimeChunk {
// MARK: - Variables // MARK: - Variables
public var seconds = 0 public var seconds = 0
@ -67,57 +66,57 @@ public struct TimeChunk {
* well defined unit of time without the context of a calendar. ! * well defined unit of time without the context of a calendar. !
*/ */
public func to(_ unit: TimeUnits) -> Int { public func to(_ unit: TimeUnits) -> Int {
if self.months != 0 { if months != 0 {
print("Months are not supported for conversion due to their uncertain number of days.") print("Months are not supported for conversion due to their uncertain number of days.")
return 0 return 0
} }
if (unit == .seconds) { if unit == .seconds {
var total = self.seconds var total = seconds
total += self.minutes * 60 total += minutes * 60
total += self.hours * 60 * 60 total += hours * 60 * 60
total += self.days * 24 * 60 * 60 total += days * 24 * 60 * 60
total += self.weeks * 7 * 24 * 60 * 60 total += weeks * 7 * 24 * 60 * 60
total += self.years * 365 * 24 * 60 * 60 total += years * 365 * 24 * 60 * 60
return total return total
} else if (unit == .minutes) { } else if unit == .minutes {
var total = self.minutes var total = minutes
total += self.seconds / 60 total += seconds / 60
total += self.hours * 60 total += hours * 60
total += self.days * 24 * 60 total += days * 24 * 60
total += self.weeks * 7 * 24 * 60 total += weeks * 7 * 24 * 60
total += self.years * 365 * 24 * 60 total += years * 365 * 24 * 60
return total return total
} else if (unit == .hours) { } else if unit == .hours {
var total = self.hours var total = hours
let secondsToMinutes = self.seconds / 60 let secondsToMinutes = seconds / 60
total += (self.minutes + secondsToMinutes) / 60 total += (minutes + secondsToMinutes) / 60
total += self.days * 24 total += days * 24
total += self.weeks * 7 * 24 total += weeks * 7 * 24
total += self.years * 365 * 24 total += years * 365 * 24
return total return total
} else if (unit == .days) { } else if unit == .days {
var total = self.days var total = days
let secondsToMinutes = self.seconds / 60 let secondsToMinutes = seconds / 60
let minutesToHours = (self.minutes + secondsToMinutes) / 60 let minutesToHours = (minutes + secondsToMinutes) / 60
total += (self.hours + minutesToHours) / 24 total += (hours + minutesToHours) / 24
total += self.weeks * 7 total += weeks * 7
total += self.years * 365 total += years * 365
return total return total
} else if (unit == .weeks) { } else if unit == .weeks {
var total = self.weeks var total = weeks
let secondsToMinutes = self.seconds / 60 let secondsToMinutes = seconds / 60
let minutesToHours = (self.minutes + secondsToMinutes) / 60 let minutesToHours = (minutes + secondsToMinutes) / 60
let hoursToDays = (self.hours + minutesToHours) / 24 let hoursToDays = (hours + minutesToHours) / 24
total += (self.days + hoursToDays) / 7 total += (days + hoursToDays) / 7
total += self.years * 52 total += years * 52
return total return total
} else if (unit == .years) { } else if unit == .years {
var total = self.years var total = years
let secondsToMinutes = self.seconds / 60 let secondsToMinutes = seconds / 60
let minutesToHours = (self.minutes + secondsToMinutes) / 60 let minutesToHours = (minutes + secondsToMinutes) / 60
let hoursToDays = (self.hours + minutesToHours) / 24 let hoursToDays = (hours + minutesToHours) / 24
let weeksToDays = weeks * 7 let weeksToDays = weeks * 7
total += (self.days + hoursToDays + weeksToDays) / 365 total += (days + hoursToDays + weeksToDays) / 365
return total return total
} }
return 0 return 0
@ -274,5 +273,4 @@ public struct TimeChunk {
invertedChunk.years = -chunk.years invertedChunk.years = -chunk.years
return invertedChunk return invertedChunk
} }
} }

217
Clocker/Dependencies/Date Additions/TimePeriod.swift

@ -9,36 +9,34 @@
import Foundation import Foundation
/** /**
* In DateTools, time periods are represented by the TimePeriod protocol. * In DateTools, time periods are represented by the TimePeriod protocol.
* Required variables and method impleementations are bound below. An inheritable * Required variables and method impleementations are bound below. An inheritable
* implementation of the TimePeriodProtocol is available through the TimePeriodClass * implementation of the TimePeriodProtocol is available through the TimePeriodClass
* *
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/ */
public protocol TimePeriodProtocol { public protocol TimePeriodProtocol {
// MARK: - Variables // MARK: - Variables
/** /**
* The start date for a TimePeriod representing the starting boundary of the time period * The start date for a TimePeriod representing the starting boundary of the time period
*/ */
var beginning: Date? {get set} var beginning: Date? { get set }
/** /**
* The end date for a TimePeriod representing the ending boundary of the time period * The end date for a TimePeriod representing the ending boundary of the time period
*/ */
var end: Date? {get set} var end: Date? { get set }
} }
public extension TimePeriodProtocol { public extension TimePeriodProtocol {
// MARK: - Information // MARK: - Information
/** /**
* True if the `TimePeriod`'s duration is zero * True if the `TimePeriod`'s duration is zero
*/ */
var isMoment: Bool { var isMoment: Bool {
return self.beginning == self.end return beginning == end
} }
/** /**
@ -46,8 +44,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var years: Int { var years: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.yearsEarlier(than: self.end!) return beginning!.yearsEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -57,8 +55,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var weeks: Int { var weeks: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.weeksEarlier(than: self.end!) return beginning!.weeksEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -68,8 +66,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var days: Int { var days: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.daysEarlier(than: self.end!) return beginning!.daysEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -79,8 +77,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var hours: Int { var hours: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.hoursEarlier(than: self.end!) return beginning!.hoursEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -90,8 +88,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var minutes: Int { var minutes: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.minutesEarlier(than: self.end!) return beginning!.minutesEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -101,8 +99,8 @@ public extension TimePeriodProtocol {
* Returns the max int if beginning or end are nil. * Returns the max int if beginning or end are nil.
*/ */
var seconds: Int { var seconds: Int {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return self.beginning!.secondsEarlier(than: self.end!) return beginning!.secondsEarlier(than: end!)
} }
return Int.max return Int.max
} }
@ -112,7 +110,7 @@ public extension TimePeriodProtocol {
* Returns a time chunk with all zeroes if beginning or end are nil. * Returns a time chunk with all zeroes if beginning or end are nil.
*/ */
var chunk: TimeChunk { var chunk: TimeChunk {
if beginning != nil && end != nil { if beginning != nil, end != nil {
return beginning!.chunkBetween(date: end!) return beginning!.chunkBetween(date: end!)
} }
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0) return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
@ -123,8 +121,8 @@ public extension TimePeriodProtocol {
* `TimePeriod` as a `TimeInterval`. * `TimePeriod` as a `TimeInterval`.
*/ */
var duration: TimeInterval { var duration: TimeInterval {
if self.beginning != nil && self.end != nil { if beginning != nil, end != nil {
return abs(self.beginning!.timeIntervalSince(self.end!)) return abs(beginning!.timeIntervalSince(end!))
} }
return TimeInterval(Double.greatestFiniteMagnitude) return TimeInterval(Double.greatestFiniteMagnitude)
@ -138,43 +136,42 @@ public extension TimePeriodProtocol {
* links: * links:
* [GitHub](https://github.com/MatthewYork/DateTools#relationships), * [GitHub](https://github.com/MatthewYork/DateTools#relationships),
* [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET) * [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET)
* *
* - parameter period: The time period to compare to self * - parameter period: The time period to compare to self
* *
* - returns: The relationship between self and the given time period * - returns: The relationship between self and the given time period
*/ */
func relation(to period: TimePeriodProtocol) -> Relation { func relation(to period: TimePeriodProtocol) -> Relation {
//Make sure that all start and end points exist for comparison // Make sure that all start and end points exist for comparison
if (self.beginning != nil && self.end != nil && period.beginning != nil && period.end != nil) { if beginning != nil, end != nil, period.beginning != nil, period.end != nil {
//Make sure time periods are of positive durations // Make sure time periods are of positive durations
if (self.beginning!.isEarlier(than: self.end!) && period.beginning!.isEarlier(than: period.end!)) { if beginning!.isEarlier(than: end!), period.beginning!.isEarlier(than: period.end!) {
// Make comparisons
//Make comparisons if period.end!.isEarlier(than: beginning!) {
if (period.end!.isEarlier(than: self.beginning!)) {
return .after return .after
} else if (period.end!.equals(self.beginning!)) { } else if period.end!.equals(beginning!) {
return .startTouching return .startTouching
} else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) { } else if period.beginning!.isEarlier(than: beginning!), period.end!.isEarlier(than: end!) {
return .startInside return .startInside
} else if (period.beginning!.equals(self.beginning!) && period.end!.isLater(than: self.end!)) { } else if period.beginning!.equals(beginning!), period.end!.isLater(than: end!) {
return .insideStartTouching return .insideStartTouching
} else if (period.beginning!.equals(self.beginning!) && period.end!.isEarlier(than: self.end!)) { } else if period.beginning!.equals(beginning!), period.end!.isEarlier(than: end!) {
return .enclosingStartTouching return .enclosingStartTouching
} else if (period.beginning!.isLater(than: self.beginning!) && period.end!.isEarlier(than: self.end!)) { } else if period.beginning!.isLater(than: beginning!), period.end!.isEarlier(than: end!) {
return .enclosing return .enclosing
} else if (period.beginning!.isLater(than: self.beginning!) && period.end!.equals(self.end!)) { } else if period.beginning!.isLater(than: beginning!), period.end!.equals(end!) {
return .enclosingEndTouching return .enclosingEndTouching
} else if (period.beginning!.equals(self.beginning!) && period.end!.equals(self.end!)) { } else if period.beginning!.equals(beginning!), period.end!.equals(end!) {
return .exactMatch return .exactMatch
} else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isLater(than: self.end!)) { } else if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: end!) {
return .inside return .inside
} else if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.equals(self.end!)) { } else if period.beginning!.isEarlier(than: beginning!), period.end!.equals(end!) {
return .insideEndTouching return .insideEndTouching
} else if (period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)) { } else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) {
return .endInside return .endInside
} else if (period.beginning!.equals(self.end!) && period.end!.isLater(than: self.end!)) { } else if period.beginning!.equals(end!), period.end!.isLater(than: end!) {
return .endTouching return .endTouching
} else if (period.beginning!.isLater(than: self.end!)) { } else if period.beginning!.isLater(than: end!) {
return .before return .before
} }
} }
@ -192,7 +189,7 @@ public extension TimePeriodProtocol {
* - returns: True if the periods are the same * - returns: True if the periods are the same
*/ */
func equals(_ period: TimePeriodProtocol) -> Bool { func equals(_ period: TimePeriodProtocol) -> Bool {
return self.beginning == period.beginning && self.end == period.end return beginning == period.beginning && end == period.end
} }
/** /**
@ -204,7 +201,7 @@ public extension TimePeriodProtocol {
* - returns: True if self is inside of the given `TimePeriod` * - returns: True if self is inside of the given `TimePeriod`
*/ */
func isInside(of period: TimePeriodProtocol) -> Bool { func isInside(of period: TimePeriodProtocol) -> Bool {
return period.beginning!.isEarlierThanOrEqual(to: self.beginning!) && period.end!.isLaterThanOrEqual(to: self.end!) return period.beginning!.isEarlierThanOrEqual(to: beginning!) && period.end!.isLaterThanOrEqual(to: end!)
} }
/** /**
@ -216,10 +213,10 @@ public extension TimePeriodProtocol {
* - returns: True if the given `TimePeriod` is inside of self * - returns: True if the given `TimePeriod` is inside of self
*/ */
func contains(_ date: Date, interval: Interval) -> Bool { func contains(_ date: Date, interval: Interval) -> Bool {
if (interval == .open) { if interval == .open {
return self.beginning!.isEarlier(than: date) && self.end!.isLater(than: date) return beginning!.isEarlier(than: date) && end!.isLater(than: date)
} else if (interval == .closed) { } else if interval == .closed {
return (self.beginning!.isEarlierThanOrEqual(to: date) && self.end!.isLaterThanOrEqual(to: date)) return (beginning!.isEarlierThanOrEqual(to: date) && end!.isLaterThanOrEqual(to: date))
} }
return false return false
@ -234,7 +231,7 @@ public extension TimePeriodProtocol {
* - returns: True if the given `TimePeriod` is inside of self * - returns: True if the given `TimePeriod` is inside of self
*/ */
func contains(_ period: TimePeriodProtocol) -> Bool { func contains(_ period: TimePeriodProtocol) -> Bool {
return self.beginning!.isEarlierThanOrEqual(to: period.beginning!) && self.end!.isLaterThanOrEqual(to: period.end!) return beginning!.isEarlierThanOrEqual(to: period.beginning!) && end!.isLaterThanOrEqual(to: period.end!)
} }
/** /**
@ -245,16 +242,16 @@ public extension TimePeriodProtocol {
* - returns: True if there is a period of time that is shared by both `TimePeriod`s * - returns: True if there is a period of time that is shared by both `TimePeriod`s
*/ */
func overlaps(with period: TimePeriodProtocol) -> Bool { func overlaps(with period: TimePeriodProtocol) -> Bool {
//Outside -> Inside // Outside -> Inside
if (period.beginning!.isEarlier(than: self.beginning!) && period.end!.isLater(than: self.beginning!)) { if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: beginning!) {
return true return true
} }
//Enclosing // Enclosing
else if (period.beginning!.isLaterThanOrEqual(to: self.beginning!) && period.end!.isEarlierThanOrEqual(to: self.end!)) { else if period.beginning!.isLaterThanOrEqual(to: beginning!), period.end!.isEarlierThanOrEqual(to: end!) {
return true return true
} }
//Inside -> Out // Inside -> Out
else if(period.beginning!.isEarlier(than: self.end!) && period.end!.isLater(than: self.end!)) { else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) {
return true return true
} }
return false return false
@ -268,7 +265,7 @@ public extension TimePeriodProtocol {
* - returns: True if there is a period of time or moment that is shared by both `TimePeriod`s * - returns: True if there is a period of time or moment that is shared by both `TimePeriod`s
*/ */
func intersects(with period: TimePeriodProtocol) -> Bool { func intersects(with period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) != .after && self.relation(to: period) != .before return relation(to: period) != .after && relation(to: period) != .before
} }
/** /**
@ -279,7 +276,7 @@ public extension TimePeriodProtocol {
* - returns: True if there is a period of time between self and the given `TimePeriod` not contained by either period * - returns: True if there is a period of time between self and the given `TimePeriod` not contained by either period
*/ */
func hasGap(between period: TimePeriodProtocol) -> Bool { func hasGap(between period: TimePeriodProtocol) -> Bool {
return self.isBefore(period: period) || self.isAfter(period: period) return isBefore(period: period) || isAfter(period: period)
} }
/** /**
@ -290,10 +287,10 @@ public extension TimePeriodProtocol {
* - returns: The gap between the periods. Zero if there is no gap. * - returns: The gap between the periods. Zero if there is no gap.
*/ */
func gap(between period: TimePeriodProtocol) -> TimeInterval { func gap(between period: TimePeriodProtocol) -> TimeInterval {
if (self.end!.isEarlier(than: period.beginning!)) { if end!.isEarlier(than: period.beginning!) {
return abs(self.end!.timeIntervalSince(period.beginning!)) return abs(end!.timeIntervalSince(period.beginning!))
} else if (period.end!.isEarlier(than: self.beginning!)) { } else if period.end!.isEarlier(than: beginning!) {
return abs(period.end!.timeIntervalSince(self.beginning!)) return abs(period.end!.timeIntervalSince(beginning!))
} }
return 0 return 0
} }
@ -307,8 +304,8 @@ public extension TimePeriodProtocol {
* - returns: The gap between the periods, zero if there is no gap * - returns: The gap between the periods, zero if there is no gap
*/ */
func gap(between period: TimePeriodProtocol) -> TimeChunk? { func gap(between period: TimePeriodProtocol) -> TimeChunk? {
if self.end != nil && period.beginning != nil { if end != nil, period.beginning != nil {
return (self.end?.chunkBetween(date: period.beginning!))! return (end?.chunkBetween(date: period.beginning!))!
} }
return nil return nil
} }
@ -321,7 +318,7 @@ public extension TimePeriodProtocol {
* - returns: True if self is after the given `TimePeriod` * - returns: True if self is after the given `TimePeriod`
*/ */
func isAfter(period: TimePeriodProtocol) -> Bool { func isAfter(period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) == .after return relation(to: period) == .after
} }
/** /**
@ -332,7 +329,7 @@ public extension TimePeriodProtocol {
* - returns: True if self is after the given `TimePeriod` * - returns: True if self is after the given `TimePeriod`
*/ */
func isBefore(period: TimePeriodProtocol) -> Bool { func isBefore(period: TimePeriodProtocol) -> Bool {
return self.relation(to: period) == .before return relation(to: period) == .before
} }
// MARK: - Shifts // MARK: - Shifts
@ -345,8 +342,8 @@ public extension TimePeriodProtocol {
* - parameter timeInterval: The time interval to shift the period by * - parameter timeInterval: The time interval to shift the period by
*/ */
mutating func shift(by timeInterval: TimeInterval) { mutating func shift(by timeInterval: TimeInterval) {
self.beginning?.addTimeInterval(timeInterval) beginning?.addTimeInterval(timeInterval)
self.end?.addTimeInterval(timeInterval) end?.addTimeInterval(timeInterval)
} }
/** /**
@ -355,8 +352,8 @@ public extension TimePeriodProtocol {
* - parameter chunk: The time chunk to shift the period by * - parameter chunk: The time chunk to shift the period by
*/ */
mutating func shift(by chunk: TimeChunk) { mutating func shift(by chunk: TimeChunk) {
self.beginning = self.beginning?.add(chunk) beginning = beginning?.add(chunk)
self.end = self.end?.add(chunk) end = end?.add(chunk)
} }
// MARK: - Lengthen / Shorten // MARK: - Lengthen / Shorten
@ -372,12 +369,12 @@ public extension TimePeriodProtocol {
mutating func lengthen(by timeInterval: TimeInterval, at anchor: Anchor) { mutating func lengthen(by timeInterval: TimeInterval, at anchor: Anchor) {
switch anchor { switch anchor {
case .beginning: case .beginning:
self.end = self.end?.addingTimeInterval(timeInterval) end = end?.addingTimeInterval(timeInterval)
case .center: case .center:
self.beginning = self.beginning?.addingTimeInterval(-timeInterval/2.0) beginning = beginning?.addingTimeInterval(-timeInterval / 2.0)
self.end = self.end?.addingTimeInterval(timeInterval/2.0) end = end?.addingTimeInterval(timeInterval / 2.0)
case .end: case .end:
self.beginning = self.beginning?.addingTimeInterval(-timeInterval) beginning = beginning?.addingTimeInterval(-timeInterval)
} }
} }
@ -390,12 +387,12 @@ public extension TimePeriodProtocol {
mutating func lengthen(by chunk: TimeChunk, at anchor: Anchor) { mutating func lengthen(by chunk: TimeChunk, at anchor: Anchor) {
switch anchor { switch anchor {
case .beginning: case .beginning:
self.end = self.end?.add(chunk) end = end?.add(chunk)
case .center: case .center:
// Do not lengthen by TimeChunk at center // Do not lengthen by TimeChunk at center
print("Mutation via chunk from center anchor is not supported.") print("Mutation via chunk from center anchor is not supported.")
case .end: case .end:
self.beginning = self.beginning?.subtract(chunk) beginning = beginning?.subtract(chunk)
} }
} }
@ -408,12 +405,12 @@ public extension TimePeriodProtocol {
mutating func shorten(by timeInterval: TimeInterval, at anchor: Anchor) { mutating func shorten(by timeInterval: TimeInterval, at anchor: Anchor) {
switch anchor { switch anchor {
case .beginning: case .beginning:
self.end = self.end?.addingTimeInterval(-timeInterval) end = end?.addingTimeInterval(-timeInterval)
case .center: case .center:
self.beginning = self.beginning?.addingTimeInterval(timeInterval/2.0) beginning = beginning?.addingTimeInterval(timeInterval / 2.0)
self.end = self.end?.addingTimeInterval(-timeInterval/2.0) end = end?.addingTimeInterval(-timeInterval / 2.0)
case .end: case .end:
self.beginning = self.beginning?.addingTimeInterval(timeInterval) beginning = beginning?.addingTimeInterval(timeInterval)
} }
} }
@ -426,26 +423,26 @@ public extension TimePeriodProtocol {
mutating func shorten(by chunk: TimeChunk, at anchor: Anchor) { mutating func shorten(by chunk: TimeChunk, at anchor: Anchor) {
switch anchor { switch anchor {
case .beginning: case .beginning:
self.end = self.end?.subtract(chunk) end = end?.subtract(chunk)
case .center: case .center:
// Do not shorten by TimeChunk at center // Do not shorten by TimeChunk at center
print("Mutation via chunk from center anchor is not supported.") print("Mutation via chunk from center anchor is not supported.")
case .end: case .end:
self.beginning = self.beginning?.add(chunk) beginning = beginning?.add(chunk)
} }
} }
} }
/** /**
* In DateTools, time periods are represented by the case TimePeriod class * In DateTools, time periods are represented by the case TimePeriod class
* and come with a suite of initializaiton, manipulation, and comparison methods * and come with a suite of initializaiton, manipulation, and comparison methods
* to make working with them a breeze. * to make working with them a breeze.
* *
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/ */
open class TimePeriod: TimePeriodProtocol { open class TimePeriod: TimePeriodProtocol {
// MARK: - Variables // MARK: - Variables
/** /**
* The start date for a TimePeriod representing the starting boundary of the time period * The start date for a TimePeriod representing the starting boundary of the time period
*/ */
@ -458,9 +455,7 @@ open class TimePeriod: TimePeriodProtocol {
// MARK: - Initializers // MARK: - Initializers
init() { init() {}
}
init(beginning: Date?, end: Date?) { init(beginning: Date?, end: Date?) {
self.beginning = beginning self.beginning = beginning
@ -469,27 +464,27 @@ open class TimePeriod: TimePeriodProtocol {
init(beginning: Date, duration: TimeInterval) { init(beginning: Date, duration: TimeInterval) {
self.beginning = beginning self.beginning = beginning
self.end = beginning + duration end = beginning + duration
} }
init(end: Date, duration: TimeInterval) { init(end: Date, duration: TimeInterval) {
self.end = end self.end = end
self.beginning = end.addingTimeInterval(-duration) beginning = end.addingTimeInterval(-duration)
} }
init(beginning: Date, chunk: TimeChunk) { init(beginning: Date, chunk: TimeChunk) {
self.beginning = beginning self.beginning = beginning
self.end = beginning + chunk end = beginning + chunk
} }
init(end: Date, chunk: TimeChunk) { init(end: Date, chunk: TimeChunk) {
self.end = end self.end = end
self.beginning = end - chunk beginning = end - chunk
} }
init(chunk: TimeChunk) { init(chunk: TimeChunk) {
self.beginning = Date() beginning = Date()
self.end = self.beginning?.add(chunk) end = beginning?.add(chunk)
} }
// MARK: - Shifted // MARK: - Shifted
@ -503,8 +498,8 @@ open class TimePeriod: TimePeriodProtocol {
*/ */
func shifted(by timeInterval: TimeInterval) -> TimePeriod { func shifted(by timeInterval: TimeInterval) -> TimePeriod {
let timePeriod = TimePeriod() let timePeriod = TimePeriod()
timePeriod.beginning = self.beginning?.addingTimeInterval(timeInterval) timePeriod.beginning = beginning?.addingTimeInterval(timeInterval)
timePeriod.end = self.end?.addingTimeInterval(timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval)
return timePeriod return timePeriod
} }
@ -517,8 +512,8 @@ open class TimePeriod: TimePeriodProtocol {
*/ */
func shifted(by chunk: TimeChunk) -> TimePeriod { func shifted(by chunk: TimeChunk) -> TimePeriod {
let timePeriod = TimePeriod() let timePeriod = TimePeriod()
timePeriod.beginning = self.beginning?.add(chunk) timePeriod.beginning = beginning?.add(chunk)
timePeriod.end = self.end?.add(chunk) timePeriod.end = end?.add(chunk)
return timePeriod return timePeriod
} }
@ -538,14 +533,14 @@ open class TimePeriod: TimePeriodProtocol {
let timePeriod = TimePeriod() let timePeriod = TimePeriod()
switch anchor { switch anchor {
case .beginning: case .beginning:
timePeriod.beginning = self.beginning timePeriod.beginning = beginning
timePeriod.end = self.end?.addingTimeInterval(timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval)
case .center: case .center:
timePeriod.beginning = self.beginning?.addingTimeInterval(-timeInterval) timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval)
timePeriod.end = self.end?.addingTimeInterval(timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval)
case .end: case .end:
timePeriod.beginning = self.beginning?.addingTimeInterval(-timeInterval) timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval)
timePeriod.end = self.end timePeriod.end = end
} }
return timePeriod return timePeriod
@ -590,8 +585,8 @@ open class TimePeriod: TimePeriodProtocol {
timePeriod.beginning = beginning timePeriod.beginning = beginning
timePeriod.end = end?.addingTimeInterval(-timeInterval) timePeriod.end = end?.addingTimeInterval(-timeInterval)
case .center: case .center:
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval/2) timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval / 2)
timePeriod.end = end?.addingTimeInterval(timeInterval/2) timePeriod.end = end?.addingTimeInterval(timeInterval / 2)
case .end: case .end:
timePeriod.beginning = beginning?.addingTimeInterval(timeInterval) timePeriod.beginning = beginning?.addingTimeInterval(timeInterval)
timePeriod.end = end timePeriod.end = end
@ -629,7 +624,7 @@ open class TimePeriod: TimePeriodProtocol {
/** /**
* Operator overload for checking if two `TimePeriod`s are equal * Operator overload for checking if two `TimePeriod`s are equal
*/ */
static func ==(leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool { static func == (leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool {
return leftAddend.equals(rightAddend) return leftAddend.equals(rightAddend)
} }
@ -637,14 +632,14 @@ open class TimePeriod: TimePeriodProtocol {
/** /**
* Operator overload for lengthening a `TimePeriod` by a `TimeInterval` * Operator overload for lengthening a `TimePeriod` by a `TimeInterval`
*/ */
static func +(leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod { static func + (leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning) return leftAddend.lengthened(by: rightAddend, at: .beginning)
} }
/** /**
* Operator overload for lengthening a `TimePeriod` by a `TimeChunk` * Operator overload for lengthening a `TimePeriod` by a `TimeChunk`
*/ */
static func +(leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod { static func + (leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning) return leftAddend.lengthened(by: rightAddend, at: .beginning)
} }
@ -652,21 +647,21 @@ open class TimePeriod: TimePeriodProtocol {
/** /**
* Operator overload for shortening a `TimePeriod` by a `TimeInterval` * Operator overload for shortening a `TimePeriod` by a `TimeInterval`
*/ */
static func -(minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod { static func - (minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning) return minuend.shortened(by: subtrahend, at: .beginning)
} }
/** /**
* Operator overload for shortening a `TimePeriod` by a `TimeChunk` * Operator overload for shortening a `TimePeriod` by a `TimeChunk`
*/ */
static func -(minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod { static func - (minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning) return minuend.shortened(by: subtrahend, at: .beginning)
} }
/** /**
* Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol` * Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol`
*/ */
static func ==(left: TimePeriod, right: TimePeriodProtocol) -> Bool { static func == (left: TimePeriod, right: TimePeriodProtocol) -> Bool {
return left.equals(right) return left.equals(right)
} }
} }

53
Clocker/Dependencies/Date Additions/TimePeriodChain.swift

@ -11,14 +11,13 @@ import Foundation
/** /**
* Time period chains serve as a tightly coupled set of time periods. They are * Time period chains serve as a tightly coupled set of time periods. They are
* always organized by start and end date, and have their own characteristics like * always organized by start and end date, and have their own characteristics like
* a StartDate and EndDate that are extrapolated from the time periods within. Time * a StartDate and EndDate that are extrapolated from the time periods within. Time
* period chains do not allow overlaps within their set of time periods. This type of * period chains do not allow overlaps within their set of time periods. This type of
* group is ideal for modeling schedules like sequential meetings or appointments. * group is ideal for modeling schedules like sequential meetings or appointments.
* *
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information.
*/ */
open class TimePeriodChain: TimePeriodGroup { open class TimePeriodChain: TimePeriodGroup {
// MARK: - Chain Existence Manipulation // MARK: - Chain Existence Manipulation
/** /**
@ -28,12 +27,12 @@ open class TimePeriodChain: TimePeriodGroup {
* - parameter period: TimePeriodProtocol to add to the collection * - parameter period: TimePeriodProtocol to add to the collection
*/ */
public func append(_ period: TimePeriodProtocol) { public func append(_ period: TimePeriodProtocol) {
let beginning = (self.periods.isEmpty == false) ? self.periods.last!.end! : period.beginning let beginning = (periods.isEmpty == false) ? periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod) periods.append(newPeriod)
//Update updateExtremes // Update updateExtremes
if periods.count == 1 { if periods.count == 1 {
_beginning = period.beginning _beginning = period.beginning
_end = period.end _end = period.end
@ -50,12 +49,12 @@ open class TimePeriodChain: TimePeriodGroup {
*/ */
public func append<G: TimePeriodGroup>(contentsOf group: G) { public func append<G: TimePeriodGroup>(contentsOf group: G) {
for period in group.periods { for period in group.periods {
let beginning = (self.periods.isEmpty == false) ? self.periods.last!.end! : period.beginning let beginning = (periods.isEmpty == false) ? periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
self.periods.append(newPeriod) periods.append(newPeriod)
//Update updateExtremes // Update updateExtremes
if periods.count == 1 { if periods.count == 1 {
_beginning = period.beginning _beginning = period.beginning
_end = period.end _end = period.end
@ -72,23 +71,23 @@ open class TimePeriodChain: TimePeriodGroup {
* - parameter index: Index to insert period at * - parameter index: Index to insert period at
*/ */
public func insert(_ period: TimePeriodProtocol, at index: Int) { public func insert(_ period: TimePeriodProtocol, at index: Int) {
//Check for special zero case which takes the beginning date // Check for special zero case which takes the beginning date
if index == 0 && period.beginning != nil && period.end != nil { if index == 0, period.beginning != nil, period.end != nil {
//Insert new period // Insert new period
periods.insert(period, at: index) periods.insert(period, at: index)
} else if period.beginning != nil && period.end != nil { } else if period.beginning != nil, period.end != nil {
//Insert new period // Insert new period
periods.insert(period, at: index) periods.insert(period, at: index)
} else { } else {
print("All TimePeriods in a TimePeriodChain must contain a defined start and end date") print("All TimePeriods in a TimePeriodChain must contain a defined start and end date")
return return
} }
//Shift all periods after inserted period // Shift all periods after inserted period
for i in 0..<periods.count { for i in 0 ..< periods.count {
if i > index && i > 0 { if i > index, i > 0 {
let currentPeriod = TimePeriod(beginning: period.beginning, end: period.end) let currentPeriod = TimePeriod(beginning: period.beginning, end: period.end)
periods[i].beginning = periods[i-1].end periods[i].beginning = periods[i - 1].end
periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration) periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration)
} }
} }
@ -102,14 +101,14 @@ open class TimePeriodChain: TimePeriodGroup {
* - parameter at: The index in the collection to remove * - parameter at: The index in the collection to remove
*/ */
public func remove(at index: Int) { public func remove(at index: Int) {
//Retrieve duration of period to be removed // Retrieve duration of period to be removed
let duration = periods[index].duration let duration = periods[index].duration
//Remove period // Remove period
periods.remove(at: index) periods.remove(at: index)
//Shift all periods after inserted period // Shift all periods after inserted period
for i in index..<periods.count { for i in index ..< periods.count {
periods[i].shift(by: -duration) periods[i].shift(by: -duration)
} }
updateExtremes() updateExtremes()
@ -119,7 +118,7 @@ open class TimePeriodChain: TimePeriodGroup {
* Remove all periods from period array. * Remove all periods from period array.
*/ */
public func removeAll() { public func removeAll() {
self.periods.removeAll() periods.removeAll()
updateExtremes() updateExtremes()
} }
@ -131,8 +130,8 @@ open class TimePeriodChain: TimePeriodGroup {
* - parameter duration: The time interval to shift the period by * - parameter duration: The time interval to shift the period by
*/ */
public func shift(by duration: TimeInterval) { public func shift(by duration: TimeInterval) {
for var period in self.periods { for var period in periods {
period.shift(by:duration) period.shift(by: duration)
} }
_beginning = _beginning?.addingTimeInterval(duration) _beginning = _beginning?.addingTimeInterval(duration)
@ -156,7 +155,7 @@ open class TimePeriodChain: TimePeriodGroup {
* *
*/ */
public func pop() -> TimePeriodProtocol? { public func pop() -> TimePeriodProtocol? {
let period = self.periods.popLast() let period = periods.popLast()
updateExtremes() updateExtremes()
return period return period
@ -172,7 +171,7 @@ open class TimePeriodChain: TimePeriodGroup {
/** /**
* Operator overload for comparing `TimePeriodChain`s to each other * Operator overload for comparing `TimePeriodChain`s to each other
*/ */
public static func ==(left: TimePeriodChain, right: TimePeriodChain) -> Bool { public static func == (left: TimePeriodChain, right: TimePeriodChain) -> Bool {
return left.equals(right) return left.equals(right)
} }
} }

56
Clocker/Dependencies/Date Additions/TimePeriodCollection.swift

@ -17,13 +17,12 @@ import Foundation
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information.
*/ */
open class TimePeriodCollection: TimePeriodGroup { open class TimePeriodCollection: TimePeriodGroup {
// MARK: - Collection Manipulation // MARK: - Collection Manipulation
/** /**
* Append a TimePeriodProtocol to the periods array and check if the Collection's * Append a TimePeriodProtocol to the periods array and check if the Collection's
* beginning and end should change. * beginning and end should change.
* *
* - parameter period: TimePeriodProtocol to add to the collection * - parameter period: TimePeriodProtocol to add to the collection
*/ */
public func append(_ period: TimePeriodProtocol) { public func append(_ period: TimePeriodProtocol) {
@ -93,12 +92,12 @@ open class TimePeriodCollection: TimePeriodGroup {
* Sort periods array in place by beginning * Sort periods array in place by beginning
*/ */
public func sortByBeginning() { public func sortByBeginning() {
self.sort { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in sort { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil { if period1.beginning == nil, period2.beginning == nil {
return false return false
} else if (period1.beginning == nil) { } else if period1.beginning == nil {
return true return true
} else if (period2.beginning == nil) { } else if period2.beginning == nil {
return false return false
} else { } else {
return period2.beginning! < period1.beginning! return period2.beginning! < period1.beginning!
@ -110,7 +109,7 @@ open class TimePeriodCollection: TimePeriodGroup {
* Sort periods array in place * Sort periods array in place
*/ */
public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) { public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
self.periods.sort(by: areInIncreasingOrder) periods.sort(by: areInIncreasingOrder)
} }
// New collection // New collection
@ -120,12 +119,12 @@ open class TimePeriodCollection: TimePeriodGroup {
* - returns: Collection with sorted periods * - returns: Collection with sorted periods
*/ */
public func sortedByBeginning() -> TimePeriodCollection { public func sortedByBeginning() -> TimePeriodCollection {
let array = self.periods.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in let array = periods.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil { if period1.beginning == nil, period2.beginning == nil {
return false return false
} else if (period1.beginning == nil) { } else if period1.beginning == nil {
return true return true
} else if (period2.beginning == nil) { } else if period2.beginning == nil {
return false return false
} else { } else {
return period2.beginning! < period1.beginning! return period2.beginning! < period1.beginning!
@ -143,7 +142,7 @@ open class TimePeriodCollection: TimePeriodGroup {
*/ */
public func sorted(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) -> TimePeriodCollection { public func sorted(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) -> TimePeriodCollection {
let collection = TimePeriodCollection() let collection = TimePeriodCollection()
collection.append(self.periods.sorted(by: areInIncreasingOrder)) collection.append(periods.sorted(by: areInIncreasingOrder))
return collection return collection
} }
@ -161,14 +160,14 @@ open class TimePeriodCollection: TimePeriodGroup {
public func allInside(in period: TimePeriodProtocol) -> TimePeriodCollection { public func allInside(in period: TimePeriodProtocol) -> TimePeriodCollection {
let collection = TimePeriodCollection() let collection = TimePeriodCollection()
// Filter by period // Filter by period
collection.periods = self.periods.filter({ (timePeriod: TimePeriodProtocol) -> Bool in collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
return timePeriod.isInside(of: period) timePeriod.isInside(of: period)
}) }
return collection return collection
} }
/** /**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing
* the given date. * the given date.
* *
* - parameter date: The date to compare each period to * - parameter date: The date to compare each period to
@ -178,14 +177,14 @@ open class TimePeriodCollection: TimePeriodGroup {
public func periodsIntersected(by date: Date) -> TimePeriodCollection { public func periodsIntersected(by date: Date) -> TimePeriodCollection {
let collection = TimePeriodCollection() let collection = TimePeriodCollection()
// Filter by period // Filter by period
collection.periods = self.periods.filter({ (timePeriod: TimePeriodProtocol) -> Bool in collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
return timePeriod.contains(date, interval: .closed) timePeriod.contains(date, interval: .closed)
}) }
return collection return collection
} }
/** /**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
* containing either the start date or the end date--or both--of the given `TimePeriod`. * containing either the start date or the end date--or both--of the given `TimePeriod`.
* *
* - parameter period: The period to compare each other period to * - parameter period: The period to compare each other period to
@ -194,10 +193,10 @@ open class TimePeriodCollection: TimePeriodGroup {
*/ */
public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection { public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection {
let collection = TimePeriodCollection() let collection = TimePeriodCollection()
//Filter by periop // Filter by periop
collection.periods = self.periods.filter({ (timePeriod: TimePeriodProtocol) -> Bool in collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
return timePeriod.intersects(with: period) timePeriod.intersects(with: period)
}) }
return collection return collection
} }
@ -219,22 +218,21 @@ open class TimePeriodCollection: TimePeriodGroup {
/** /**
* Operator overload for comparing `TimePeriodCollection`s to each other * Operator overload for comparing `TimePeriodCollection`s to each other
*/ */
public static func ==(left: TimePeriodCollection, right: TimePeriodCollection) -> Bool { public static func == (left: TimePeriodCollection, right: TimePeriodCollection) -> Bool {
return left.equals(right) return left.equals(right)
} }
// MARK: - Helpers // MARK: - Helpers
internal func updateExtremes(period: TimePeriodProtocol) { internal func updateExtremes(period: TimePeriodProtocol) {
//Check incoming period against previous beginning and end date // Check incoming period against previous beginning and end date
if self.count == 1 { if count == 1 {
_beginning = period.beginning _beginning = period.beginning
_end = period.end _end = period.end
} else { } else {
_beginning = nilOrEarlier(date1: _beginning, date2: period.beginning) _beginning = nilOrEarlier(date1: _beginning, date2: period.beginning)
_end = nilOrLater(date1: _end, date2: period.end) _end = nilOrLater(date1: _end, date2: period.end)
} }
} }
internal func updateExtremes() { internal func updateExtremes() {
@ -244,7 +242,7 @@ open class TimePeriodCollection: TimePeriodGroup {
} else { } else {
_beginning = periods[0].beginning _beginning = periods[0].beginning
_end = periods[0].end _end = periods[0].end
for i in 1..<periods.count { for i in 1 ..< periods.count {
_beginning = nilOrEarlier(date1: _beginning, date2: periods[i].beginning) _beginning = nilOrEarlier(date1: _beginning, date2: periods[i].beginning)
_end = nilOrEarlier(date1: _end, date2: periods[i].end) _end = nilOrEarlier(date1: _end, date2: periods[i].end)
} }

33
Clocker/Dependencies/Date Additions/TimePeriodGroup.swift

@ -8,15 +8,14 @@
import Foundation import Foundation
/** /**
* Time period groups are the final abstraction of date and time in DateTools. Here, time * Time period groups are the final abstraction of date and time in DateTools. Here, time
* periods are gathered and organized into something useful. There are two main types of time * periods are gathered and organized into something useful. There are two main types of time
* period groups, `TimePeriodCollection` and `TimePeriodChain`. * period groups, `TimePeriodCollection` and `TimePeriodChain`.
* *
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information. * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information.
*/ */
open class TimePeriodGroup: Sequence { open class TimePeriodGroup: Sequence {
// MARK: - Variables // MARK: - Variables
/** /**
@ -57,7 +56,7 @@ open class TimePeriodGroup: Sequence {
* periods array. Nil if any beginning or end date in any contained period is nil. * periods array. Nil if any beginning or end date in any contained period is nil.
*/ */
public var duration: TimeInterval? { public var duration: TimeInterval? {
if beginning != nil && end != nil { if beginning != nil, end != nil {
return end!.timeIntervalSince(beginning!) return end!.timeIntervalSince(beginning!)
} }
return nil return nil
@ -65,9 +64,7 @@ open class TimePeriodGroup: Sequence {
// MARK: - Initializers // MARK: - Initializers
public init() { public init() {}
}
// MARK: - Comparisons // MARK: - Comparisons
@ -79,12 +76,12 @@ open class TimePeriodGroup: Sequence {
* - returns: True if the periods arrays are the same * - returns: True if the periods arrays are the same
*/ */
public func equals(_ group: TimePeriodGroup) -> Bool { public func equals(_ group: TimePeriodGroup) -> Bool {
return containSameElements(array1: self.periods, group.periods) return containSameElements(array1: periods, group.periods)
} }
// MARK: - Sequence Protocol // MARK: - Sequence Protocol
public func makeIterator() -> IndexingIterator<Array<TimePeriodProtocol>> { public func makeIterator() -> IndexingIterator<[TimePeriodProtocol]> {
return periods.makeIterator() return periods.makeIterator()
} }
@ -105,9 +102,7 @@ open class TimePeriodGroup: Sequence {
} }
subscript(index: Int) -> TimePeriodProtocol { subscript(index: Int) -> TimePeriodProtocol {
get { return periods[index]
return periods[index]
}
} }
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
@ -120,28 +115,28 @@ open class TimePeriodGroup: Sequence {
} }
var compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in var compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil { if period1.beginning == nil, period2.beginning == nil {
return false return false
} else if (period1.beginning == nil) { } else if period1.beginning == nil {
return true return true
} else if (period2.beginning == nil) { } else if period2.beginning == nil {
return false return false
} else { } else {
return period2.beginning! < period1.beginning! return period2.beginning! < period1.beginning!
} }
} }
var compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in var compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil && period2.beginning == nil { if period1.beginning == nil, period2.beginning == nil {
return false return false
} else if (period1.beginning == nil) { } else if period1.beginning == nil {
return true return true
} else if (period2.beginning == nil) { } else if period2.beginning == nil {
return false return false
} else { } else {
return period2.beginning! < period1.beginning! return period2.beginning! < period1.beginning!
} }
} }
for x in 0..<compArray1.count { for x in 0 ..< compArray1.count {
if !compArray1[x].equals(compArray2[x]) { if !compArray1[x].equals(compArray2[x]) {
return false return false
} }

38
Clocker/Dependencies/Solar.swift

@ -4,21 +4,20 @@ import Cocoa
import CoreLocation import CoreLocation
public struct Solar { public struct Solar {
/// The coordinate that is used for the calculation /// The coordinate that is used for the calculation
public let coordinate: CLLocationCoordinate2D public let coordinate: CLLocationCoordinate2D
/// The date to generate sunrise / sunset times for /// The date to generate sunrise / sunset times for
public fileprivate(set) var date: Date public private(set) var date: Date
public fileprivate(set) var sunrise: Date? public private(set) var sunrise: Date?
public fileprivate(set) var sunset: Date? public private(set) var sunset: Date?
public fileprivate(set) var civilSunrise: Date? public private(set) var civilSunrise: Date?
public fileprivate(set) var civilSunset: Date? public private(set) var civilSunset: Date?
public fileprivate(set) var nauticalSunrise: Date? public private(set) var nauticalSunrise: Date?
public fileprivate(set) var nauticalSunset: Date? public private(set) var nauticalSunset: Date?
public fileprivate(set) var astronomicalSunrise: Date? public private(set) var astronomicalSunrise: Date?
public fileprivate(set) var astronomicalSunset: Date? public private(set) var astronomicalSunset: Date?
// MARK: Init // MARK: Init
@ -52,20 +51,20 @@ public struct Solar {
// MARK: - Private functions // MARK: - Private functions
fileprivate enum SunriseSunset { private enum SunriseSunset {
case sunrise case sunrise
case sunset case sunset
} }
/// Used for generating several of the possible sunrise / sunset times /// Used for generating several of the possible sunrise / sunset times
fileprivate enum Zenith: Double { private enum Zenith: Double {
case official = 90.83 case official = 90.83
case civil = 96 case civil = 96
case nautical = 102 case nautical = 102
case astronimical = 108 case astronimical = 108
} }
fileprivate func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? { private func calculate(_ sunriseSunset: SunriseSunset, for date: Date, and zenith: Zenith) -> Date? {
guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil } guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil }
// Get the day of the year // Get the day of the year
@ -147,7 +146,7 @@ public struct Solar {
if shouldBeYesterday { if shouldBeYesterday {
setDate = Date(timeInterval: -(60 * 60 * 24), since: date) setDate = Date(timeInterval: -(60 * 60 * 24), since: date)
} else if shouldBeTomorrow { } else if shouldBeTomorrow {
setDate = Date(timeInterval: (60 * 60 * 24), since: date) setDate = Date(timeInterval: 60 * 60 * 24, since: date)
} else { } else {
setDate = date setDate = date
} }
@ -162,7 +161,7 @@ public struct Solar {
} }
/// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum` /// Normalises a value between 0 and `maximum`, by adding or subtracting `maximum`
fileprivate func normalise(_ value: Double, withMaximum maximum: Double) -> Double { private func normalise(_ value: Double, withMaximum maximum: Double) -> Double {
var value = value var value = value
if value < 0 { if value < 0 {
@ -175,24 +174,22 @@ public struct Solar {
return value return value
} }
} }
extension Solar { extension Solar {
/// Whether the location specified by the `latitude` and `longitude` is in daytime on `date` /// Whether the location specified by the `latitude` and `longitude` is in daytime on `date`
/// - Complexity: O(1) /// - Complexity: O(1)
public var isDaytime: Bool { public var isDaytime: Bool {
guard guard
let sunrise = sunrise, let sunrise = sunrise,
let sunset = sunset let sunset = sunset
else { else {
return false return false
} }
let beginningOfDay = sunrise.timeIntervalSince1970 let beginningOfDay = sunrise.timeIntervalSince1970
let endOfDay = sunset.timeIntervalSince1970 let endOfDay = sunset.timeIntervalSince1970
let currentTime = self.date.timeIntervalSince1970 let currentTime = date.timeIntervalSince1970
let isSunriseOrLater = currentTime >= beginningOfDay let isSunriseOrLater = currentTime >= beginningOfDay
let isBeforeSunset = currentTime < endOfDay let isBeforeSunset = currentTime < endOfDay
@ -205,7 +202,6 @@ extension Solar {
public var isNighttime: Bool { public var isNighttime: Bool {
return !isDaytime return !isDaytime
} }
} }
// MARK: - Helper extensions // MARK: - Helper extensions

17
Clocker/Events and Reminders/CalendarHandler.swift

@ -94,17 +94,17 @@ extension EventCenter {
let relevantEvents = filteredEvents[autoupdatingCalendar.startOfDay(for: Date())] ?? [] let relevantEvents = filteredEvents[autoupdatingCalendar.startOfDay(for: Date())] ?? []
let filteredEvent = relevantEvents.filter({ let filteredEvent = relevantEvents.filter {
$0.event.isAllDay == false && $0.event.startDate.timeIntervalSinceNow > 0 $0.event.isAllDay == false && $0.event.startDate.timeIntervalSinceNow > 0
}).first }.first
if let firstEvent = filteredEvent { if let firstEvent = filteredEvent {
return firstEvent.event return firstEvent.event
} }
let filteredAllDayEvent = relevantEvents.filter({ let filteredAllDayEvent = relevantEvents.filter {
$0.isAllDay $0.isAllDay
}).first }.first
return filteredAllDayEvent?.event return filteredAllDayEvent?.event
} }
@ -113,7 +113,7 @@ extension EventCenter {
store.requestAccess(to: entity) { [weak self] granted, _ in store.requestAccess(to: entity) { [weak self] granted, _ in
// On successful granting of calendar permission, we default to showing events from all calendars // On successful granting of calendar permission, we default to showing events from all calendars
if let `self` = self, entity == .event, granted { if let self = self, entity == .event, granted {
self.saveDefaultIdentifiersList() self.saveDefaultIdentifiersList()
} }
@ -149,7 +149,7 @@ extension EventCenter {
func saveDefaultIdentifiersList() { func saveDefaultIdentifiersList() {
OperationQueue.main.addOperation { [weak self] in OperationQueue.main.addOperation { [weak self] in
guard let `self` = self else { return } guard let self = self else { return }
let allCalendars = self.retrieveAllCalendarIdentifiers() let allCalendars = self.retrieveAllCalendarIdentifiers()
if !allCalendars.isEmpty { if !allCalendars.isEmpty {
@ -162,7 +162,7 @@ extension EventCenter {
func retrieveAllCalendarIdentifiers() -> [String] { func retrieveAllCalendarIdentifiers() -> [String] {
return store.calendars(for: .event).map { (calendar) -> String in return store.calendars(for: .event).map { (calendar) -> String in
return calendar.calendarIdentifier calendar.calendarIdentifier
} }
} }
@ -172,7 +172,7 @@ extension EventCenter {
guard let convertedDate = calendar?.date(byAdding: dateComps, guard let convertedDate = calendar?.date(byAdding: dateComps,
to: Date(), to: Date(),
options: NSCalendar.Options.matchFirst) else { options: NSCalendar.Options.matchFirst) else {
return Date() return Date()
} }
return convertedDate return convertedDate
} }
@ -211,7 +211,6 @@ extension EventCenter {
// We map eachDate to array of events happening on that day // We map eachDate to array of events happening on that day
for event in events where shouldSkipEvent(event) == false { for event in events where shouldSkipEvent(event) == false {
// Iterate through the days this event spans. We only care about // Iterate through the days this event spans. We only care about
// days for this event that are between startDate and endDate // days for this event that are between startDate and endDate
let eventStartDate = event.startDate as NSDate let eventStartDate = event.startDate as NSDate

2
Clocker/Events and Reminders/EventCenter.swift

@ -29,7 +29,7 @@ class EventCenter: NSObject {
object: nil) object: nil)
} }
@objc func eventStoreDidChange(_ sender: Any) { @objc func eventStoreDidChange(_: Any) {
refetchAll() refetchAll()
} }

5
Clocker/Events and Reminders/RemindersHandler.swift

@ -3,7 +3,6 @@
import EventKit import EventKit
extension EventCenter { extension EventCenter {
// MARK: Private helper methods // MARK: Private helper methods
private func retrieveCalendar() -> EKCalendar? { private func retrieveCalendar() -> EKCalendar? {
@ -11,7 +10,7 @@ extension EventCenter {
let calendars = store.calendars(for: .reminder) let calendars = store.calendars(for: .reminder)
let calendarTitle = "Clocker Reminders" let calendarTitle = "Clocker Reminders"
let predicate = NSPredicate(format: "title matches %@", calendarTitle) let predicate = NSPredicate(format: "title matches %@", calendarTitle)
let filtered = calendars.filter({ predicate.evaluate(with: $0) }) let filtered = calendars.filter { predicate.evaluate(with: $0) }
if !filtered.isEmpty { if !filtered.isEmpty {
calendar = filtered.first calendar = filtered.first
@ -83,7 +82,7 @@ extension EventCenter {
private func addAlarmIfNeccesary(for event: EKReminder, _ selection: Int) { private func addAlarmIfNeccesary(for event: EKReminder, _ selection: Int) {
if selection != 0 { if selection != 0 {
var offset: TimeInterval = 0 var offset: TimeInterval = 0
switch selection { switch selection {
case 2: case 2:
offset = -300 offset = -300

12
Clocker/Menu Bar/MenubarHandler.swift

@ -5,7 +5,6 @@ import EventKit
class MenubarHandler: NSObject { class MenubarHandler: NSObject {
@objc func titleForMenubar() -> String? { @objc func titleForMenubar() -> String? {
if let nextEvent = checkForUpcomingEvents() { if let nextEvent = checkForUpcomingEvents() {
return nextEvent return nextEvent
} }
@ -20,11 +19,11 @@ class MenubarHandler: NSObject {
} }
if menubarTitles.isEmpty == false { if menubarTitles.isEmpty == false {
let titles = menubarTitles.map({ (data) -> String? in let titles = menubarTitles.map { (data) -> String? in
let timezone = TimezoneData.customObject(from: data) let timezone = TimezoneData.customObject(from: data)
let operationsObject = TimezoneDataOperations(with: timezone!) let operationsObject = TimezoneDataOperations(with: timezone!)
return "\(operationsObject.menuTitle().trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines))" return "\(operationsObject.menuTitle().trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines))"
}) }
let titlesStringified = titles.compactMap { $0 } let titlesStringified = titles.compactMap { $0 }
return titlesStringified.joined(separator: " ") return titlesStringified.joined(separator: " ")
@ -34,9 +33,7 @@ class MenubarHandler: NSObject {
} }
private func checkForUpcomingEvents() -> String? { private func checkForUpcomingEvents() -> String? {
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
let filteredDates = EventCenter.sharedCenter().eventsForDate let filteredDates = EventCenter.sharedCenter().eventsForDate
let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar
guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else { guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else {
@ -44,13 +41,10 @@ class MenubarHandler: NSObject {
} }
for event in events { for event in events {
if event.event.startDate.timeIntervalSinceNow > 0, !event.isAllDay {
if event.event.startDate.timeIntervalSinceNow > 0 && !event.isAllDay {
let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60 let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60
if timeForEventToStart > 30 { if timeForEventToStart > 30 {
print("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") print("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins")
continue continue

8
Clocker/Menu Bar/StatusContainerView.swift

@ -21,7 +21,6 @@ func bufferCalculatedWidth() -> Int {
} }
func compactWidth(for timezone: TimezoneData) -> Int { func compactWidth(for timezone: TimezoneData) -> Int {
var totalWidth = 55 var totalWidth = 55
let timeFormat = timezone.timezoneFormat() let timeFormat = timezone.timezoneFormat()
@ -47,7 +46,6 @@ func compactWidth(for timezone: TimezoneData) -> Int {
let bufferWidth: CGFloat = 9.5 let bufferWidth: CGFloat = 9.5
class StatusContainerView: NSView { class StatusContainerView: NSView {
private var previousX: Int = 0 private var previousX: Int = 0
override func awakeFromNib() { override func awakeFromNib() {
@ -57,7 +55,6 @@ class StatusContainerView: NSView {
} }
init(with timezones: [Data]) { init(with timezones: [Data]) {
func addSubviews() { func addSubviews() {
timezones.forEach { timezones.forEach {
if let timezoneObject = TimezoneData.customObject(from: $0) { if let timezoneObject = TimezoneData.customObject(from: $0) {
@ -91,12 +88,11 @@ class StatusContainerView: NSView {
addSubviews() addSubviews()
} }
required init?(coder decoder: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func addTimezone(_ timezone: TimezoneData) { func addTimezone(_ timezone: TimezoneData) {
let calculatedWidth = bestWidth(for: timezone) let calculatedWidth = bestWidth(for: timezone)
let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30)
@ -118,7 +114,6 @@ class StatusContainerView: NSView {
} }
func updateTime() { func updateTime() {
if subviews.isEmpty { if subviews.isEmpty {
assertionFailure("Subviews count should > 0") assertionFailure("Subviews count should > 0")
} }
@ -129,5 +124,4 @@ class StatusContainerView: NSView {
} }
} }
} }
} }

9
Clocker/Menu Bar/StatusItemHandler.swift

@ -9,7 +9,6 @@ private enum MenubarState {
} }
class StatusItemHandler: NSObject { class StatusItemHandler: NSObject {
var hasActiveIcon: Bool = false var hasActiveIcon: Bool = false
var menubarTimer: Timer? var menubarTimer: Timer?
@ -112,8 +111,8 @@ class StatusItemHandler: NSObject {
object: nil) object: nil)
userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification, userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification,
object: self, object: self,
queue: mainQueue) { _ in queue: mainQueue) { _ in
self.setupStatusItem() self.setupStatusItem()
} }
} }
@ -219,7 +218,7 @@ class StatusItemHandler: NSObject {
let shouldDisplaySeconds = shouldDisplaySecondsInMenubar() let shouldDisplaySeconds = shouldDisplaySecondsInMenubar()
let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites)
if !units.contains(.second) && shouldDisplaySeconds { if !units.contains(.second), shouldDisplaySeconds {
units.insert(.second) units.insert(.second)
} }
@ -281,7 +280,7 @@ class StatusItemHandler: NSObject {
let menubarFavourites = (DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data]) ?? [] let menubarFavourites = (DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data]) ?? []
if menubarFavourites.isEmpty && DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { if menubarFavourites.isEmpty, DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false {
print("Invalidating menubar timer!") print("Invalidating menubar timer!")
invalidation() invalidation()

23
Clocker/Menu Bar/StatusItemView.swift

@ -14,26 +14,26 @@ var compactModeTimeFont: NSFont {
} }
var timeAttributes: [NSAttributedString.Key: AnyObject] { var timeAttributes: [NSAttributedString.Key: AnyObject] {
let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black
let attributes = [ let attributes = [
NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
] ]
return attributes return attributes
} }
class StatusItemView: NSView { class StatusItemView: NSView {
// MARK: Private variables // MARK: Private variables
private let locationView: NSTextField = NSTextField(labelWithString: "Hello") private let locationView: NSTextField = NSTextField(labelWithString: "Hello")
private let timeView: NSTextField = NSTextField(labelWithString: "Mon 19:14 PM") private let timeView: NSTextField = NSTextField(labelWithString: "Mon 19:14 PM")
private var operationsObject: TimezoneDataOperations { private var operationsObject: TimezoneDataOperations {
return TimezoneDataOperations(with: dataObject) return TimezoneDataOperations(with: dataObject)
} }
private var textFontAttributes: [NSAttributedString.Key: Any] { private var textFontAttributes: [NSAttributedString.Key: Any] {
let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black let textColor = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" ? NSColor.white : NSColor.black
@ -41,12 +41,13 @@ class StatusItemView: NSView {
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10),
NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
] ]
return textFontAttributes return textFontAttributes
} }
// MARK: Public // MARK: Public
var dataObject: TimezoneData! { var dataObject: TimezoneData! {
didSet { didSet {
initialSetup() initialSetup()
@ -69,15 +70,15 @@ class StatusItemView: NSView {
locationView.leadingAnchor.constraint(equalTo: leadingAnchor), locationView.leadingAnchor.constraint(equalTo: leadingAnchor),
locationView.trailingAnchor.constraint(equalTo: trailingAnchor), locationView.trailingAnchor.constraint(equalTo: trailingAnchor),
locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7), locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7),
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35) locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35),
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
timeView.leadingAnchor.constraint(equalTo: leadingAnchor), timeView.leadingAnchor.constraint(equalTo: leadingAnchor),
timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
timeView.topAnchor.constraint(equalTo: locationView.bottomAnchor), timeView.topAnchor.constraint(equalTo: locationView.bottomAnchor),
timeView.bottomAnchor.constraint(equalTo: bottomAnchor) timeView.bottomAnchor.constraint(equalTo: bottomAnchor),
]) ])
} }
func updateTimeInMenubar() { func updateTimeInMenubar() {
@ -89,7 +90,7 @@ class StatusItemView: NSView {
timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes) timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuHeader(), attributes: timeAttributes)
} }
required init?(coder decoder: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }

66
Clocker/Onboarding/OnboardingParentViewController.swift

@ -58,7 +58,6 @@ class OnboardingParentViewController: NSViewController {
} }
private func setupUI() { private func setupUI() {
setIdentifiersForTests() setIdentifiersForTests()
positiveButton.title = "Get Started" positiveButton.title = "Get Started"
@ -119,9 +118,9 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideLeft) { options: .slideLeft) {
self.positiveButton.tag = OnboardingType.permissions.rawValue self.positiveButton.tag = OnboardingType.permissions.rawValue
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.backButton.isHidden = false self.backButton.isHidden = false
} }
} }
@ -136,10 +135,10 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideLeft) { options: .slideLeft) {
self.backButton.tag = OnboardingType.permissions.rawValue self.backButton.tag = OnboardingType.permissions.rawValue
self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue
self.positiveButton.title = "Open Clocker At Login" self.positiveButton.title = "Open Clocker At Login"
self.negativeButton.isHidden = false self.negativeButton.isHidden = false
} }
} }
@ -156,10 +155,10 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideLeft) { options: .slideLeft) {
self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue
self.positiveButton.tag = OnboardingType.search.rawValue self.positiveButton.tag = OnboardingType.search.rawValue
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
} }
} }
@ -174,15 +173,14 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideLeft) { options: .slideLeft) {
self.backButton.tag = OnboardingType.search.rawValue self.backButton.tag = OnboardingType.search.rawValue
self.positiveButton.tag = OnboardingType.final.rawValue self.positiveButton.tag = OnboardingType.final.rawValue
self.positiveButton.title = "Launch Clocker" self.positiveButton.title = "Launch Clocker"
} }
} }
private func performFinalStepsBeforeFinishing() { private func performFinalStepsBeforeFinishing() {
self.positiveButton.tag = OnboardingType.complete.rawValue positiveButton.tag = OnboardingType.complete.rawValue
// Install the menubar option! // Install the menubar option!
let appDelegate = NSApplication.shared.delegate as? AppDelegate let appDelegate = NSApplication.shared.delegate as? AppDelegate
@ -222,10 +220,10 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.search.rawValue self.positiveButton.tag = OnboardingType.search.rawValue
self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
} }
} }
@ -238,10 +236,10 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue
self.backButton.tag = OnboardingType.permissions.rawValue self.backButton.tag = OnboardingType.permissions.rawValue
self.positiveButton.title = "Open Clocker At Login" self.positiveButton.title = "Open Clocker At Login"
self.negativeButton.isHidden = false self.negativeButton.isHidden = false
} }
} }
@ -256,10 +254,10 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.permissions.rawValue self.positiveButton.tag = OnboardingType.permissions.rawValue
self.backButton.tag = OnboardingType.welcome.rawValue self.backButton.tag = OnboardingType.welcome.rawValue
self.negativeButton.isHidden = true self.negativeButton.isHidden = true
self.positiveButton.title = "Continue" self.positiveButton.title = "Continue"
} }
} }
@ -272,14 +270,13 @@ class OnboardingParentViewController: NSViewController {
transition(from: fromViewController, transition(from: fromViewController,
to: toViewController, to: toViewController,
options: .slideRight) { options: .slideRight) {
self.positiveButton.tag = OnboardingType.welcome.rawValue self.positiveButton.tag = OnboardingType.welcome.rawValue
self.backButton.isHidden = true self.backButton.isHidden = true
self.positiveButton.title = "Get Started" self.positiveButton.title = "Get Started"
} }
} }
private func shouldStartAtLogin(_ shouldStart: Bool) { private func shouldStartAtLogin(_ shouldStart: Bool) {
// If tests are going on, we don't want to enable/disable launch at login! // If tests are going on, we don't want to enable/disable launch at login!
if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) { if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) {
return return
@ -307,7 +304,6 @@ class OnboardingParentViewController: NSViewController {
} }
private func currentController() -> [String: String] { private func currentController() -> [String: String] {
switch positiveButton.tag { switch positiveButton.tag {
case 0: case 0:
return ["Onboarding Process Interrupted": "Welcome View"] return ["Onboarding Process Interrupted": "Welcome View"]
@ -322,7 +318,7 @@ class OnboardingParentViewController: NSViewController {
case 5: case 5:
return ["Onboarding Process Completed": "Successfully"] return ["Onboarding Process Completed": "Successfully"]
default: default:
return ["Onboarding Process Interrupted": "Error"] return ["Onboarding Process Interrupted": "Error"]
} }
} }
} }

3
Clocker/Onboarding/OnboardingPermissionsViewController.swift

@ -91,7 +91,7 @@ class OnboardingPermissionsViewController: NSViewController {
eventCenter.requestAccess(to: .event, completionHandler: { [weak self] granted in eventCenter.requestAccess(to: .event, completionHandler: { [weak self] granted in
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
guard let `self` = self else { return } guard let self = self else { return }
self.calendarActivityIndicator.stopAnimation(nil) self.calendarActivityIndicator.stopAnimation(nil)
@ -130,7 +130,6 @@ class OnboardingPermissionsViewController: NSViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)

94
Clocker/Onboarding/OnboardingSearchController.swift

@ -14,7 +14,7 @@ class OnboardingSearchController: NSViewController {
@IBOutlet private var searchBar: ClockerSearchField! @IBOutlet private var searchBar: ClockerSearchField!
@IBOutlet private var resultsTableView: NSTableView! @IBOutlet private var resultsTableView: NSTableView!
@IBOutlet private var accessoryLabel: NSTextField! @IBOutlet private var accessoryLabel: NSTextField!
@IBOutlet weak var undoButton: NSButton! @IBOutlet var undoButton: NSButton!
private var results: [TimezoneData] = [] private var results: [TimezoneData] = []
private var dataTask: URLSessionDataTask? = .none private var dataTask: URLSessionDataTask? = .none
@ -57,7 +57,7 @@ class OnboardingSearchController: NSViewController {
@objc func doubleClickAction(_: NSTableView?) { @objc func doubleClickAction(_: NSTableView?) {
[accessoryLabel].forEach { $0?.isHidden = false } [accessoryLabel].forEach { $0?.isHidden = false }
if resultsTableView.selectedRow >= 0 && resultsTableView.selectedRow < results.count { if resultsTableView.selectedRow >= 0, resultsTableView.selectedRow < results.count {
let selectedTimezone = results[resultsTableView.selectedRow] let selectedTimezone = results[resultsTableView.selectedRow]
addTimezoneToDefaults(selectedTimezone) addTimezoneToDefaults(selectedTimezone)
@ -65,13 +65,12 @@ class OnboardingSearchController: NSViewController {
} }
private func addTimezoneToDefaults(_ timezone: TimezoneData) { private func addTimezoneToDefaults(_ timezone: TimezoneData) {
func setupLabelHidingTimer() { func setupLabelHidingTimer() {
Timer.scheduledTimer(withTimeInterval: 5, Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in repeats: false) { _ in
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.accessoryLabel.stringValue = CLEmptyString self.accessoryLabel.stringValue = CLEmptyString
} }
} }
} }
@ -104,7 +103,6 @@ class OnboardingSearchController: NSViewController {
/// Returns true if there's an error. /// Returns true if there's an error.
private func handleEdgeCase(for response: Data?) -> Bool { private func handleEdgeCase(for response: Data?) -> Bool {
func setErrorPlaceholders() { func setErrorPlaceholders() {
setInfoLabel("No timezone found! Try entering an exact name.") setInfoLabel("No timezone found! Try entering an exact name.")
searchBar.placeholderString = placeholders.randomElement() searchBar.placeholderString = placeholders.randomElement()
@ -150,7 +148,7 @@ class OnboardingSearchController: NSViewController {
NetworkManager.task(with: urlString) { [weak self] response, error in NetworkManager.task(with: urlString) { [weak self] response, error in
guard let `self` = self else { return } guard let self = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if self.handleEdgeCase(for: response) == true { if self.handleEdgeCase(for: response) == true {
@ -158,7 +156,7 @@ class OnboardingSearchController: NSViewController {
} }
if error == nil, let json = response, let response = self.decodeTimezone(from: json) { if error == nil, let json = response, let response = self.decodeTimezone(from: json) {
if self.resultsTableView.selectedRow >= 0 && self.resultsTableView.selectedRow < self.results.count { if self.resultsTableView.selectedRow >= 0, self.resultsTableView.selectedRow < self.results.count {
var filteredAddress = "Error" var filteredAddress = "Error"
if let address = dataObject.formattedAddress { if let address = dataObject.formattedAddress {
@ -172,7 +170,7 @@ class OnboardingSearchController: NSViewController {
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
"nextUpdate": CLEmptyString, "nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress CLCustomLabel: filteredAddress,
] as [String: Any] ] as [String: Any]
DataStore.shared().addTimezone(TimezoneData(with: newTimeZone)) DataStore.shared().addTimezone(TimezoneData(with: newTimeZone))
@ -222,7 +220,6 @@ class OnboardingSearchController: NSViewController {
} }
@IBAction func search(_ sender: NSSearchField) { @IBAction func search(_ sender: NSSearchField) {
resultsTableView.deselectAll(nil) resultsTableView.deselectAll(nil)
let searchString = sender.stringValue let searchString = sender.stringValue
@ -249,9 +246,8 @@ class OnboardingSearchController: NSViewController {
} }
@objc func actualSearch() { @objc func actualSearch() {
func setupForError() { func setupForError() {
self.resultsTableView.isHidden = true resultsTableView.isHidden = true
} }
let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US" let userPreferredLanguage = Locale.preferredLanguages.first ?? "en-US"
@ -267,56 +263,55 @@ class OnboardingSearchController: NSViewController {
dataTask = NetworkManager.task(with: urlString, dataTask = NetworkManager.task(with: urlString,
completionHandler: { [weak self] response, error in completionHandler: { [weak self] response, error in
guard let `self` = self else { return } guard let self = self else { return }
OperationQueue.main.addOperation {
print("Search string was: \(searchString)") OperationQueue.main.addOperation {
print("Search string was: \(searchString)")
let currentSearchBarValue = self.searchBar.stringValue let currentSearchBarValue = self.searchBar.stringValue
let words = currentSearchBarValue.components(separatedBy: CharacterSet.whitespacesAndNewlines) let words = currentSearchBarValue.components(separatedBy: CharacterSet.whitespacesAndNewlines)
if words.joined(separator: CLEmptyString) != searchString { if words.joined(separator: CLEmptyString) != searchString {
return return
} }
self.results = [] self.results = []
if let errorPresent = error { if let errorPresent = error {
self.presentErrorMessage(errorPresent.localizedDescription) self.presentErrorMessage(errorPresent.localizedDescription)
setupForError() setupForError()
return return
} }
guard let data = response else { guard let data = response else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage) self.setInfoLabel(PreferencesConstants.tryAgainMessage)
setupForError() setupForError()
return return
} }
let searchResults = self.decode(from: data) let searchResults = self.decode(from: data)
if searchResults?.status == "ZERO_RESULTS" { if searchResults?.status == "ZERO_RESULTS" {
self.setInfoLabel("No results! 😔 Try entering the exact name.") self.setInfoLabel("No results! 😔 Try entering the exact name.")
setupForError() setupForError()
return return
} }
self.appendResultsToFilteredArray(searchResults!.results) self.appendResultsToFilteredArray(searchResults!.results)
self.setInfoLabel(CLEmptyString) self.setInfoLabel(CLEmptyString)
self.resultsTableView.reloadData() self.resultsTableView.reloadData()
} }
}) })
} }
private func presentErrorMessage(_ errorMessage: String) { private func presentErrorMessage(_ errorMessage: String) {
if errorMessage == PreferencesConstants.offlineErrorMessage { if errorMessage == PreferencesConstants.offlineErrorMessage {
self.setInfoLabel(PreferencesConstants.noInternetConnectivityError) setInfoLabel(PreferencesConstants.noInternetConnectivityError)
} else { } else {
self.setInfoLabel(PreferencesConstants.tryAgainMessage) setInfoLabel(PreferencesConstants.tryAgainMessage)
} }
} }
@ -333,8 +328,8 @@ class OnboardingSearchController: NSViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: $0.placeId CLPlaceIdentifier: $0.placeId,
] as [String: Any] ] as [String: Any]
self.results.append(TimezoneData(with: totalPackage)) self.results.append(TimezoneData(with: totalPackage))
} }
@ -359,11 +354,10 @@ class OnboardingSearchController: NSViewController {
searchBar.placeholderString = placeholders.randomElement() searchBar.placeholderString = placeholders.randomElement()
} }
@IBAction func undoAction(_ sender: Any) { @IBAction func undoAction(_: Any) {
DataStore.shared().removeLastTimezone() DataStore.shared().removeLastTimezone()
setInfoLabel("Removed.") setInfoLabel("Removed.")
} }
} }
extension OnboardingSearchController: NSTableViewDataSource { extension OnboardingSearchController: NSTableViewDataSource {
@ -385,7 +379,7 @@ extension OnboardingSearchController: NSTableViewDataSource {
extension OnboardingSearchController: NSTableViewDelegate { extension OnboardingSearchController: NSTableViewDelegate {
func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat { func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat {
if row == 0 && results.isEmpty { if row == 0, results.isEmpty {
return 30 return 30
} }

11
Clocker/Overall App/AppDefaults.swift

@ -3,13 +3,12 @@
import Cocoa import Cocoa
class AppDefaults { class AppDefaults {
class func initialize() { class func initialize() {
initializeDefaults() initializeDefaults()
} }
private class func deleteOldUserDefaults() { private class func deleteOldUserDefaults() {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
// Now delete the old preferences // Now delete the old preferences
if let bundleID = Bundle.main.bundleIdentifier, userDefaults.object(forKey: "PreferencesHaveBeenWiped") == nil { if let bundleID = Bundle.main.bundleIdentifier, userDefaults.object(forKey: "PreferencesHaveBeenWiped") == nil {
@ -19,7 +18,6 @@ class AppDefaults {
} }
private class func initializeDefaults() { private class func initializeDefaults() {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
let menubarFavourites = userDefaults.object(forKey: CLMenubarFavorites) let menubarFavourites = userDefaults.object(forKey: CLMenubarFavorites)
@ -43,7 +41,6 @@ class AppDefaults {
// If we already have timezones to display in menubar, do nothing. // If we already have timezones to display in menubar, do nothing.
// Else, we switch the menubar mode default to compact mode for new users // Else, we switch the menubar mode default to compact mode for new users
if userDefaults.bool(forKey: CLDefaultMenubarMode) == false { if userDefaults.bool(forKey: CLDefaultMenubarMode) == false {
if let menubarFavourites = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data], menubarFavourites.isEmpty == false { if let menubarFavourites = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data], menubarFavourites.isEmpty == false {
userDefaults.set(1, forKey: CLMenubarCompactMode) userDefaults.set(1, forKey: CLMenubarCompactMode)
} else { } else {
@ -54,11 +51,9 @@ class AppDefaults {
} }
if userDefaults.bool(forKey: CLSwitchToCompactModeAlert) == false { if userDefaults.bool(forKey: CLSwitchToCompactModeAlert) == false {
userDefaults.set(true, forKey: CLSwitchToCompactModeAlert) userDefaults.set(true, forKey: CLSwitchToCompactModeAlert)
if let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count > 1 { if let menubarFavourites = DataStore.shared().retrieve(key: CLMenubarFavorites) as? [Data], menubarFavourites.count > 1 {
// If the user is already using the compact mode, abort. // If the user is already using the compact mode, abort.
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if DataStore.shared().shouldDisplay(.menubarCompactMode) {
return return
@ -78,7 +73,6 @@ class AppDefaults {
// Set default to System theme for Mojave // Set default to System theme for Mojave
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
if defaults.bool(forKey: CLDefaultThemeOnMojave) == false { if defaults.bool(forKey: CLDefaultThemeOnMojave) == false {
if isDarkModeOn() { if isDarkModeOn() {
Themer.shared().set(theme: 2) Themer.shared().set(theme: 2)
@ -139,16 +133,13 @@ class AppDefaults {
CLAppDislayOptions: 0, CLAppDislayOptions: 0,
CLMenubarCompactMode: 1] CLMenubarCompactMode: 1]
} }
} }
extension UserDefaults { extension UserDefaults {
func wipeIfNeccesary() { func wipeIfNeccesary() {
if let bundleID = Bundle.main.bundleIdentifier, object(forKey: "PreferencesHaveBeenWiped") == nil { if let bundleID = Bundle.main.bundleIdentifier, object(forKey: "PreferencesHaveBeenWiped") == nil {
removePersistentDomain(forName: bundleID) removePersistentDomain(forName: bundleID)
set(true, forKey: "PreferencesHaveBeenWiped") set(true, forKey: "PreferencesHaveBeenWiped")
} }
} }
} }

6
Clocker/Overall App/AppKit + Additions.swift

@ -1,7 +1,6 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
extension NSTextField { extension NSTextField {
func applyDefaultStyle() { func applyDefaultStyle() {
backgroundColor = NSColor.clear backgroundColor = NSColor.clear
isEditable = false isEditable = false
@ -19,24 +18,21 @@ extension NSTextField {
cell?.wraps = false cell?.wraps = false
cell?.isScrollable = true cell?.isScrollable = true
} }
} }
extension NSFont { extension NSFont {
func size(_ string: String, _ width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize { func size(_ string: String, _ width: Double, attributes: [NSAttributedString.Key: AnyObject]) -> CGSize {
let size = CGSize(width: width, let size = CGSize(width: width,
height: Double.greatestFiniteMagnitude) height: Double.greatestFiniteMagnitude)
var otherAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font: self] var otherAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.font: self]
attributes.forEach { (arg) in let (key, value) = arg; otherAttributes[key] = value } attributes.forEach { arg in let (key, value) = arg; otherAttributes[key] = value }
return NSString(string: string).boundingRect(with: size, return NSString(string: string).boundingRect(with: size,
options: NSString.DrawingOptions.usesLineFragmentOrigin, options: NSString.DrawingOptions.usesLineFragmentOrigin,
attributes: attributes).size attributes: attributes).size
} }
} }
class ClockerSearchField: NSSearchField { class ClockerSearchField: NSSearchField {

2
Clocker/Overall App/DateFormatterManager.swift

@ -46,7 +46,7 @@ class DateFormatterManager: NSObject {
return specializedFormatter return specializedFormatter
} }
@objc class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale: Locale = Locale.autoupdatingCurrent) -> DateFormatter { @objc class func localizedFormatter(with format: String, for timezoneIdentifier: String, locale _: Locale = Locale.autoupdatingCurrent) -> DateFormatter {
dateFormatter.dateStyle = .none dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none dateFormatter.timeStyle = .none
dateFormatter.locale = Locale.autoupdatingCurrent dateFormatter.locale = Locale.autoupdatingCurrent

2
Clocker/Overall App/Reach.swift

@ -76,7 +76,7 @@ extension ReachabilityStatus {
let connectionRequired = flags.contains(.connectionRequired) let connectionRequired = flags.contains(.connectionRequired)
let isReachable = flags.contains(.reachable) let isReachable = flags.contains(.reachable)
if !connectionRequired && isReachable { if !connectionRequired, isReachable {
self = .online(.wiFi) self = .online(.wiFi)
} else { } else {
self = .offline self = .offline

5
Clocker/Overall App/Themer.swift

@ -76,7 +76,6 @@ extension Themer {
} }
setAppAppearance() setAppAppearance()
} }
@objc func respondToInterfaceStyle() { @objc func respondToInterfaceStyle() {
@ -315,7 +314,6 @@ extension Themer {
} }
func currentLocationImage() -> NSImage { func currentLocationImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -365,7 +363,6 @@ extension Themer {
} }
func privacyTabImage() -> NSImage { func privacyTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -381,7 +378,6 @@ extension Themer {
} }
func appearanceTabImage() -> NSImage { func appearanceTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:
@ -397,7 +393,6 @@ extension Themer {
} }
func calendarTabImage() -> NSImage { func calendarTabImage() -> NSImage {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:

4
Clocker/Overall App/Timer.swift

@ -126,7 +126,7 @@ open class Repeater: Equatable {
/// Current state of the timer /// Current state of the timer
public private(set) var state: State = .paused { public private(set) var state: State = .paused {
didSet { didSet {
self.onStateChanged?(self, state) onStateChanged?(self, state)
} }
} }
@ -334,7 +334,7 @@ open class Repeater: Equatable {
/// Pause a running timer. If timer is paused it does nothing. /// Pause a running timer. If timer is paused it does nothing.
@discardableResult @discardableResult
public func pause() -> Bool { public func pause() -> Bool {
guard state != .paused && state != .finished else { guard state != .paused, state != .finished else {
return false return false
} }

1
Clocker/Overall App/UserDefaults + KVOExtensions.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
extension UserDefaults { extension UserDefaults {
@objc dynamic var displayFutureSlider: Int { @objc dynamic var displayFutureSlider: Int {
return integer(forKey: CLDisplayFutureSliderKey) return integer(forKey: CLDisplayFutureSliderKey)
} }

9
Clocker/Panel/Data Layer/TimezoneData.swift

@ -12,7 +12,6 @@ struct DateFormat {
// Non-class type cannot conform to NSCoding! // Non-class type cannot conform to NSCoding!
class TimezoneData: NSObject, NSCoding { class TimezoneData: NSObject, NSCoding {
enum SelectionType: Int { enum SelectionType: Int {
case city case city
case timezone case timezone
@ -193,7 +192,7 @@ class TimezoneData: NSObject, NSCoding {
private class func logOldModelUsage() { private class func logOldModelUsage() {
guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String, guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else {
return return
} }
let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion let operatingSystem = ProcessInfo.processInfo.operatingSystemVersion
@ -202,7 +201,7 @@ class TimezoneData: NSObject, NSCoding {
let feedbackInfo = [ let feedbackInfo = [
AppFeedbackConstants.CLOperatingSystemVersion: osVersion, AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo AppFeedbackConstants.CLClockerVersion: versionInfo,
] ]
Logger.log(object: feedbackInfo, for: "CLTimezoneData is still being used!") Logger.log(object: feedbackInfo, for: "CLTimezoneData is still being used!")
@ -256,7 +255,7 @@ class TimezoneData: NSObject, NSCoding {
// Do the serialization // Do the serialization
let serializedModels = newModels.map { (place) -> Data in let serializedModels = newModels.map { (place) -> Data in
return NSKeyedArchiver.archivedData(withRootObject: place) NSKeyedArchiver.archivedData(withRootObject: place)
} }
return serializedModels return serializedModels
@ -361,7 +360,7 @@ class TimezoneData: NSObject, NSCoding {
let errorDictionary = [ let errorDictionary = [
"Formatted Address": name, "Formatted Address": name,
"Place Identifier": placeIdentifier, "Place Identifier": placeIdentifier,
"TimezoneID": timezoneIdentifier "TimezoneID": timezoneIdentifier,
] ]
Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData") Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData")

19
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -33,7 +33,6 @@ extension TimezoneDataOperations {
} }
func compactMenuHeader() -> String { func compactMenuHeader() -> String {
var subtitle = CLEmptyString var subtitle = CLEmptyString
let shouldDayBeShown = DataStore.shared().shouldShowDateInMenubar() let shouldDayBeShown = DataStore.shared().shouldShowDateInMenubar()
@ -105,7 +104,7 @@ extension TimezoneDataOperations {
let sourceTimezone = TimeZone.current let sourceTimezone = TimeZone.current
let destinationTimezone = TimeZone(identifier: dataObject.timezone()) let destinationTimezone = TimeZone(identifier: dataObject.timezone())
let sourceGMTOffset: Double = Double(sourceTimezone.secondsFromGMT(for: source)) let sourceGMTOffset: Double = Double(sourceTimezone.secondsFromGMT(for: source))
let destinationGMTOffset: Double = Double(destinationTimezone?.secondsFromGMT(for: source) ?? 0) let destinationGMTOffset: Double = Double(destinationTimezone?.secondsFromGMT(for: source) ?? 0)
let interval = destinationGMTOffset - sourceGMTOffset let interval = destinationGMTOffset - sourceGMTOffset
@ -120,8 +119,8 @@ extension TimezoneDataOperations {
} }
return calendar?.date(byAdding: .minute, return calendar?.date(byAdding: .minute,
value: minutesToAdd, value: minutesToAdd,
to: Date()) ?? Date() to: Date()) ?? Date()
} }
func date(with sliderValue: Int, displayType: CLDateDisplayType) -> String { func date(with sliderValue: Int, displayType: CLDateDisplayType) -> String {
@ -136,10 +135,8 @@ extension TimezoneDataOperations {
} }
if displayType == CLDateDisplayType.panelDisplay { if displayType == CLDateDisplayType.panelDisplay {
// Yesterday, tomorrow, etc // Yesterday, tomorrow, etc
if relativeDayPreference.intValue == 0 { if relativeDayPreference.intValue == 0 {
let localFormatter = DateFormatterManager.localizedSimpleFormatter("EEEE") let localFormatter = DateFormatterManager.localizedSimpleFormatter("EEEE")
let local = localFormatter.date(from: localeDate(with: "EEEE")) let local = localFormatter.date(from: localeDate(with: "EEEE"))
@ -169,9 +166,9 @@ extension TimezoneDataOperations {
} }
let errorDictionary: [String: Any] = ["Timezone": dataObject.timezone(), let errorDictionary: [String: Any] = ["Timezone": dataObject.timezone(),
"Current Locale": Locale.autoupdatingCurrent.identifier, "Current Locale": Locale.autoupdatingCurrent.identifier,
"Slider Value": sliderValue, "Slider Value": sliderValue,
"Today's Date": Date()] "Today's Date": Date()]
Logger.log(object: errorDictionary, for: "Unable to get date") Logger.log(object: errorDictionary, for: "Unable to get date")
return "Error" return "Error"
@ -207,8 +204,8 @@ extension TimezoneDataOperations {
let unableToConvertDateParameters = [ let unableToConvertDateParameters = [
"New Date": newDate, "New Date": newDate,
"Timezone": dataObject.timezone(), "Timezone": dataObject.timezone(),
"Locale": dateFormatter.locale.identifier "Locale": dateFormatter.locale.identifier,
] as [String: Any] ] as [String: Any]
Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil") Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil")
return CLEmptyString return CLEmptyString
} }

2
Clocker/Panel/FloatingWindowController.swift

@ -55,7 +55,7 @@ class FloatingWindowController: ParentPanelController {
target.image = Themer.shared().extraOptionsHighlightedImage() target.image = Themer.shared().extraOptionsHighlightedImage()
if popover.isShown && row == previousPopoverRow { if popover.isShown, row == previousPopoverRow {
popover.close() popover.close()
target.image = Themer.shared().extraOptionsImage() target.image = Themer.shared().extraOptionsImage()
previousPopoverRow = -1 previousPopoverRow = -1

8
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
class NotesPopover: NSViewController { class NotesPopover: NSViewController {
private enum OverrideType { private enum OverrideType {
case timezoneFormat case timezoneFormat
case seconds case seconds
@ -37,7 +36,7 @@ class NotesPopover: NSViewController {
@IBOutlet var timeFormatControl: NSSegmentedControl! @IBOutlet var timeFormatControl: NSSegmentedControl!
@IBOutlet weak var secondsFormatControl: NSSegmentedControl! @IBOutlet var secondsFormatControl: NSSegmentedControl!
@IBOutlet var notesTextView: TextViewWithPlaceholder! @IBOutlet var notesTextView: TextViewWithPlaceholder!
@ -62,7 +61,7 @@ class NotesPopover: NSViewController {
"1 hour before", "1 hour before",
"2 hour before", "2 hour before",
"1 day before", "1 day before",
"2 days before" "2 days before",
] ]
alertPopupButton.removeAllItems() alertPopupButton.removeAllItems()
@ -200,7 +199,7 @@ class NotesPopover: NSViewController {
let attributesDictionary = [ let attributesDictionary = [
NSAttributedString.Key.font: font, NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.paragraphStyle: style NSAttributedString.Key.paragraphStyle: style,
] ]
button.attributedTitle = NSAttributedString(string: title, button.attributedTitle = NSAttributedString(string: title,
@ -448,7 +447,6 @@ class NotesPopover: NSViewController {
handler.setupStatusItem() handler.setupStatusItem()
} }
} }
} }
@objc extension NotesPopover { @objc extension NotesPopover {

4
Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift

@ -18,7 +18,7 @@ class TextViewWithPlaceholder: NSTextView {
if let placeHolderFont = NSFont(name: "Avenir", size: 14) { if let placeHolderFont = NSFont(name: "Avenir", size: 14) {
let textDict = [ let textDict = [
NSAttributedString.Key.foregroundColor: NSColor.gray, NSAttributedString.Key.foregroundColor: NSColor.gray,
NSAttributedString.Key.font: placeHolderFont NSAttributedString.Key.font: placeHolderFont,
] ]
return NSAttributedString(string: " Add your notes here.", attributes: textDict) return NSAttributedString(string: " Add your notes here.", attributes: textDict)
} }
@ -32,7 +32,7 @@ class TextViewWithPlaceholder: NSTextView {
override func draw(_ dirtyRect: NSRect) { override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect) super.draw(dirtyRect)
if string == CLEmptyString && self != window?.firstResponder { if string == CLEmptyString, self != window?.firstResponder {
placeholder.draw(at: NSPoint(x: 0, y: 0)) placeholder.draw(at: NSPoint(x: 0, y: 0))
} }
} }

13
Clocker/Panel/PanelController.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
class PanelController: ParentPanelController { class PanelController: ParentPanelController {
@objc dynamic var hasActivePanel: Bool = false @objc dynamic var hasActivePanel: Bool = false
static var sharedWindow = PanelController(windowNibName: .panel) static var sharedWindow = PanelController(windowNibName: .panel)
@ -76,7 +75,7 @@ class PanelController: ParentPanelController {
setTimezoneDatasourceSlider(sliderValue: 0) setTimezoneDatasourceSlider(sliderValue: 0)
reviewView.isHidden = !(RateController.canPrompt()) reviewView.isHidden = !RateController.canPrompt()
reviewView.layer?.backgroundColor = NSColor.clear.cgColor reviewView.layer?.backgroundColor = NSColor.clear.cgColor
@ -100,7 +99,6 @@ class PanelController: ParentPanelController {
// New way to set the panel's frame. // New way to set the panel's frame.
// This takes into account the screen's dimensions. // This takes into account the screen's dimensions.
private func setPanelFrame() { private func setPanelFrame() {
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else {
return return
} }
@ -114,7 +112,7 @@ class PanelController: ParentPanelController {
statusView = appDelegate.statusItemForPanel().statusItem.button statusView = appDelegate.statusItemForPanel().statusItem.button
} }
if let statusWindow = statusBackgroundWindow, if let statusWindow = statusBackgroundWindow,
let statusButton = statusView { let statusButton = statusView {
var statusItemFrame = statusWindow.convertToScreen(statusButton.frame) var statusItemFrame = statusWindow.convertToScreen(statusButton.frame)
var statusItemScreen = NSScreen.main var statusItemScreen = NSScreen.main
@ -176,14 +174,13 @@ class PanelController: ParentPanelController {
"Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No", "Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No",
"Country": country, "Country": country,
"Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No", "Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No",
"Number of Timezones": preferences.count "Number of Timezones": preferences.count,
] ]
Logger.log(object: panelEvent, for: "openedPanel") Logger.log(object: panelEvent, for: "openedPanel")
} }
private func startWindowTimer() { private func startWindowTimer() {
stopMenubarTimerIfNeccesary() stopMenubarTimerIfNeccesary()
if let timer = parentTimer, timer.state == .paused { if let timer = parentTimer, timer.state == .paused {
@ -195,7 +192,6 @@ class PanelController: ParentPanelController {
} }
private func startTimer() { private func startTimer() {
print("Start timer called") print("Start timer called")
parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in parentTimer = Repeater(interval: .seconds(1), mode: .infinite) { _ in
@ -204,7 +200,6 @@ class PanelController: ParentPanelController {
} }
} }
parentTimer!.start() parentTimer!.start()
} }
private func stopMenubarTimerIfNeccesary() { private func stopMenubarTimerIfNeccesary() {
@ -278,7 +273,7 @@ class PanelController: ParentPanelController {
target.image = Themer.shared().extraOptionsHighlightedImage() target.image = Themer.shared().extraOptionsHighlightedImage()
if popover.isShown && row == previousPopoverRow { if popover.isShown, row == previousPopoverRow {
popover.close() popover.close()
target.image = Themer.shared().extraOptionsImage() target.image = Themer.shared().extraOptionsImage()
previousPopoverRow = -1 previousPopoverRow = -1

42
Clocker/Panel/ParentPanelController.swift

@ -12,7 +12,6 @@ struct PanelConstants {
} }
class ParentPanelController: NSWindowController { class ParentPanelController: NSWindowController {
private var futureSliderObserver: NSKeyValueObservation? private var futureSliderObserver: NSKeyValueObservation?
private var userFontSizeSelectionObserver: NSKeyValueObservation? private var userFontSizeSelectionObserver: NSKeyValueObservation?
private var futureSliderRangeObserver: NSKeyValueObservation? private var futureSliderRangeObserver: NSKeyValueObservation?
@ -98,13 +97,13 @@ class ParentPanelController: NSWindowController {
} }
private func setupObservers() { private func setupObservers() {
futureSliderObserver = UserDefaults.standard.observe(\.displayFutureSlider, options: [.new]) { (_, change) in futureSliderObserver = UserDefaults.standard.observe(\.displayFutureSlider, options: [.new]) { _, change in
if let changedValue = change.newValue { if let changedValue = change.newValue {
self.futureSliderView.isHidden = changedValue == 1 self.futureSliderView.isHidden = changedValue == 1
} }
} }
userFontSizeSelectionObserver = UserDefaults.standard.observe(\.userFontSize, options: [.new]) { (_, change) in userFontSizeSelectionObserver = UserDefaults.standard.observe(\.userFontSize, options: [.new]) { _, change in
if let newFontSize = change.newValue { if let newFontSize = change.newValue {
Logger.log(object: ["FontSize": newFontSize], for: "User Font Size Preference") Logger.log(object: ["FontSize": newFontSize], for: "User Font Size Preference")
self.mainTableView.reloadData() self.mainTableView.reloadData()
@ -112,7 +111,7 @@ class ParentPanelController: NSWindowController {
} }
} }
futureSliderRangeObserver = UserDefaults.standard.observe(\.sliderDayRange, options: [.new]) { (_, change) in futureSliderRangeObserver = UserDefaults.standard.observe(\.sliderDayRange, options: [.new]) { _, change in
if change.newValue != nil { if change.newValue != nil {
self.adjustFutureSliderBasedOnPreferences() self.adjustFutureSliderBasedOnPreferences()
} }
@ -153,7 +152,7 @@ class ParentPanelController: NSWindowController {
themeChanged() themeChanged()
futureSliderView.isHidden = !(DataStore.shared().shouldDisplay(.futureSlider)) futureSliderView.isHidden = !DataStore.shared().shouldDisplay(.futureSlider)
sharingButton.sendAction(on: .leftMouseDown) sharingButton.sendAction(on: .leftMouseDown)
@ -168,16 +167,15 @@ class ParentPanelController: NSWindowController {
} }
private func showDebugVersionViewIfNeccesary() { private func showDebugVersionViewIfNeccesary() {
if debugVersionView != nil { if debugVersionView != nil {
debugVersionView.wantsLayer = true debugVersionView.wantsLayer = true
debugVersionView.layer?.backgroundColor = NSColor.systemRed.cgColor debugVersionView.layer?.backgroundColor = NSColor.systemRed.cgColor
} }
#if RELEASE #if RELEASE
if debugVersionView != nil && stackView.arrangedSubviews.contains(debugVersionView) { if debugVersionView != nil, stackView.arrangedSubviews.contains(debugVersionView) {
stackView.removeView(debugVersionView) stackView.removeView(debugVersionView)
} }
#endif #endif
} }
@ -277,7 +275,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13) NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13),
] ]
let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes) let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes)
@ -830,7 +828,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)! NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
] ]
leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes) leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes)
rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes) rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes)
@ -841,7 +839,7 @@ class ParentPanelController: NSWindowController {
return return
} }
NSAnimationContext.runAnimationGroup({ (context) in NSAnimationContext.runAnimationGroup({ context in
context.duration = 1 context.duration = 1
context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
leftButton.animator().alphaValue = 0.0 leftButton.animator().alphaValue = 0.0
@ -849,7 +847,7 @@ class ParentPanelController: NSWindowController {
}, completionHandler: { }, completionHandler: {
field.stringValue = title field.stringValue = title
NSAnimationContext.runAnimationGroup({ (context) in NSAnimationContext.runAnimationGroup({ context in
context.duration = 1 context.duration = 1
context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
self.runAnimationCompletionBlock(leftTitle, rightTitle) self.runAnimationCompletionBlock(leftTitle, rightTitle)
@ -858,27 +856,27 @@ class ParentPanelController: NSWindowController {
} }
private func runAnimationCompletionBlock(_ leftButtonTitle: String, _ rightButtonTitle: String) { private func runAnimationCompletionBlock(_ leftButtonTitle: String, _ rightButtonTitle: String) {
self.leftButton.animator().alphaValue = 1.0 leftButton.animator().alphaValue = 1.0
self.rightButton.animator().alphaValue = 1.0 rightButton.animator().alphaValue = 1.0
let paragraphStyle = NSMutableParagraphStyle() let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center paragraphStyle.alignment = .center
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)! NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
] ]
if self.leftButton.attributedTitle.string == "Not Really" { if leftButton.attributedTitle.string == "Not Really" {
self.leftButton.animator().attributedTitle = NSAttributedString(string: PanelConstants.noThanksTitle, attributes: styleAttributes) leftButton.animator().attributedTitle = NSAttributedString(string: PanelConstants.noThanksTitle, attributes: styleAttributes)
} }
if self.rightButton.attributedTitle.string == PanelConstants.yesWithExclamation { if rightButton.attributedTitle.string == PanelConstants.yesWithExclamation {
self.rightButton.animator().attributedTitle = NSAttributedString(string: "Yes, sure", attributes: styleAttributes) rightButton.animator().attributedTitle = NSAttributedString(string: "Yes, sure", attributes: styleAttributes)
} }
self.leftButton.animator().attributedTitle = NSAttributedString(string: leftButtonTitle, attributes: styleAttributes) leftButton.animator().attributedTitle = NSAttributedString(string: leftButtonTitle, attributes: styleAttributes)
self.rightButton.animator().attributedTitle = NSAttributedString(string: rightButtonTitle, attributes: styleAttributes) rightButton.animator().attributedTitle = NSAttributedString(string: rightButtonTitle, attributes: styleAttributes)
} }
// MARK: Date Picker + Slider // MARK: Date Picker + Slider

8
Clocker/Panel/Rate Controller/RateController.swift

@ -3,9 +3,8 @@
import Cocoa import Cocoa
final class RateController { final class RateController {
private static var storage = UserDefaults.standard private static var storage = UserDefaults.standard
private static let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A" private static let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A"
private static var debugging = false private static var debugging = false
private enum Keys { private enum Keys {
@ -43,12 +42,12 @@ final class RateController {
// Check if the app has been installed for atleast 7 days // Check if the app has been installed for atleast 7 days
guard let install = storage.object(forKey: Keys.install) as? Date, guard let install = storage.object(forKey: Keys.install) as? Date,
install.timeIntervalSinceNow < minInstall install.timeIntervalSinceNow < minInstall
else { return false } else { return false }
// If we have never been prompted before, go ahead and prompt // If we have never been prompted before, go ahead and prompt
guard let lastPrompt = storage.object(forKey: Keys.lastPrompt) as? Date, guard let lastPrompt = storage.object(forKey: Keys.lastPrompt) as? Date,
let lastVersion = storage.object(forKey: Keys.lastVersion) as? String let lastVersion = storage.object(forKey: Keys.lastVersion) as? String
else { return true } else { return true }
// Minimum interval between two versions should be 45 // Minimum interval between two versions should be 45
let minInterval: TimeInterval = day * 45 let minInterval: TimeInterval = day * 45
@ -66,5 +65,4 @@ final class RateController {
NSWorkspace.shared.open(ratingsURL) NSWorkspace.shared.open(ratingsURL)
prompted() prompted()
} }
} }

4
Clocker/Panel/Rate Controller/ReviewView.swift

@ -7,7 +7,7 @@ class ReviewView: NSView {
override func mouseEntered(with event: NSEvent) { override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event) super.mouseEntered(with: event)
let dismissalButton = subviews.filter({ $0.tag == 55 }).first let dismissalButton = subviews.filter { $0.tag == 55 }.first
if let firstMatch = dismissalButton, firstMatch.isHidden { if let firstMatch = dismissalButton, firstMatch.isHidden {
firstMatch.isHidden = false firstMatch.isHidden = false
} }
@ -15,7 +15,7 @@ class ReviewView: NSView {
override func mouseExited(with event: NSEvent) { override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event) super.mouseExited(with: event)
let dismissalButton = subviews.filter({ $0.tag == 55 }).first let dismissalButton = subviews.filter { $0.tag == 55 }.first
if let firstMatch = dismissalButton, !firstMatch.isHidden { if let firstMatch = dismissalButton, !firstMatch.isHidden {
firstMatch.isHidden = true firstMatch.isHidden = true
} }

4
Clocker/Panel/UI/CustomSliderCell.swift

@ -21,8 +21,8 @@ class CustomSliderCell: NSSliderCell {
leftRect.size.width = finalWidth leftRect.size.width = finalWidth
let background = NSBezierPath(roundedRect: rect, let background = NSBezierPath(roundedRect: rect,
xRadius: barRadius, xRadius: barRadius,
yRadius: barRadius) yRadius: barRadius)
NSColor(calibratedRed: 67.0 / 255.0, green: 138.0 / 255.0, blue: 250.0 / 255.0, alpha: 1.0).setFill() NSColor(calibratedRed: 67.0 / 255.0, green: 138.0 / 255.0, blue: 250.0 / 255.0, alpha: 1.0).setFill()
background.fill() background.fill()

12
Clocker/Panel/UI/NoTimezoneView.swift

@ -6,9 +6,9 @@ import QuartzCore
class NoTimezoneView: NSView { class NoTimezoneView: NSView {
private lazy var emoji: NSTextField = { private lazy var emoji: NSTextField = {
let emoji = NSTextField(frame: NSRect(x: frame.size.width / 2 - 50, let emoji = NSTextField(frame: NSRect(x: frame.size.width / 2 - 50,
y: frame.size.height / 2 - 50, y: frame.size.height / 2 - 50,
width: 100, width: 100,
height: 100)) height: 100))
emoji.wantsLayer = true emoji.wantsLayer = true
emoji.stringValue = "🌏" emoji.stringValue = "🌏"
emoji.isBordered = false emoji.isBordered = false
@ -23,9 +23,9 @@ class NoTimezoneView: NSView {
private lazy var message: NSTextField = { private lazy var message: NSTextField = {
let messageField = NSTextField(frame: NSRect(x: frame.size.width / 2 - 250, let messageField = NSTextField(frame: NSRect(x: frame.size.width / 2 - 250,
y: frame.size.height / 2 - 275, y: frame.size.height / 2 - 275,
width: 500, width: 500,
height: 200)) height: 200))
messageField.wantsLayer = true messageField.wantsLayer = true
messageField.setAccessibilityIdentifier("NoTimezoneMessage") messageField.setAccessibilityIdentifier("NoTimezoneMessage")
messageField.placeholderString = "No places added" messageField.placeholderString = "No places added"

2
Clocker/Panel/UI/PanelTableView.swift

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

2
Clocker/Panel/UI/TimezoneCellView.swift

@ -116,7 +116,7 @@ class TimezoneCellView: NSTableCellView {
var searchView = superview var searchView = superview
while searchView != nil && searchView is PanelTableView == false { while searchView != nil, searchView is PanelTableView == false {
searchView = searchView?.superview searchView = searchView?.superview
} }

4
Clocker/Panel/UI/TimezoneDataSource.swift

@ -60,7 +60,7 @@ extension TimezoneDataSource: NSTableViewDataSource, NSTableViewDelegate {
cellView.time.stringValue = operation.time(with: sliderValue) cellView.time.stringValue = operation.time(with: sliderValue)
cellView.noteLabel.stringValue = currentModel.note ?? CLEmptyString cellView.noteLabel.stringValue = currentModel.note ?? CLEmptyString
cellView.noteLabel.toolTip = currentModel.note ?? CLEmptyString cellView.noteLabel.toolTip = currentModel.note ?? CLEmptyString
cellView.currentLocationIndicator.isHidden = !(currentModel.isSystemTimezone) cellView.currentLocationIndicator.isHidden = !currentModel.isSystemTimezone
cellView.time.setAccessibilityIdentifier("ActualTime") cellView.time.setAccessibilityIdentifier("ActualTime")
cellView.layout(with: currentModel) cellView.layout(with: currentModel)
@ -177,7 +177,7 @@ extension TimezoneCellView {
sunriseImage.isHidden = !shouldDisplay sunriseImage.isHidden = !shouldDisplay
// If it's a timezone and not a place, we can't determine the sunrise/sunset time; hide the sunrise image // If it's a timezone and not a place, we can't determine the sunrise/sunset time; hide the sunrise image
if model.selectionType == .timezone && (model.latitude == nil && model.longitude == nil) { if model.selectionType == .timezone, model.latitude == nil, model.longitude == nil {
sunriseImage.isHidden = true sunriseImage.isHidden = true
} }

23
Clocker/Preferences/About/AboutViewController.swift

@ -33,7 +33,7 @@ class AboutViewController: ParentViewController {
setup() setup()
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.setup() self.setup()
} }
} }
@ -46,16 +46,14 @@ class AboutViewController: ParentViewController {
private func underlineTextForActionButton() { private func underlineTextForActionButton() {
let rangesInOrder = [NSRange(location: 3, length: 8), let rangesInOrder = [NSRange(location: 3, length: 8),
NSRange(location: 7, length: privateFeedback.attributedTitle.length - 7), NSRange(location: 7, length: privateFeedback.attributedTitle.length - 7),
NSRange(location: 27, length: 33), NSRange(location: 27, length: 33),
NSRange(location: 32, length: 30) NSRange(location: 32, length: 30)]
]
let buttonsInOrder = [quickCommentAction, let buttonsInOrder = [quickCommentAction,
privateFeedback, privateFeedback,
supportClocker, supportClocker,
openSourceButton openSourceButton]
]
let localizedKeys = ["1. @n0shake on Twitter for quick comments", let localizedKeys = ["1. @n0shake on Twitter for quick comments",
"2. For Private Feedback", "2. For Private Feedback",
@ -87,10 +85,10 @@ class AboutViewController: ParentViewController {
value: Themer.shared().mainTextColor(), value: Themer.shared().mainTextColor(),
range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
originalText.addAttribute(NSAttributedString.Key.font, originalText.addAttribute(NSAttributedString.Key.font,
value: (button?.font)! , value: (button?.font)!,
range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
originalText.addAttribute(NSAttributedString.Key.paragraphStyle, originalText.addAttribute(NSAttributedString.Key.paragraphStyle,
value: mutableParaghStyle , value: mutableParaghStyle,
range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count)) range: NSRange(location: 0, length: underlinedButton.attributedTitle.string.count))
underlinedButton.attributedTitle = originalText underlinedButton.attributedTitle = originalText
} }
@ -126,7 +124,6 @@ class AboutViewController: ParentViewController {
let custom: [String: Any] = ["Country": countryCode] let custom: [String: Any] = ["Country": countryCode]
Logger.log(object: custom, for: "Report Issue Opened") Logger.log(object: custom, for: "Report Issue Opened")
} }
} }
@IBAction func openGitHub(_: Any) { @IBAction func openGitHub(_: Any) {
@ -139,7 +136,7 @@ class AboutViewController: ParentViewController {
Logger.log(object: custom, for: "Opened GitHub") Logger.log(object: custom, for: "Opened GitHub")
} }
@IBOutlet weak var feedbackLabel: NSTextField! @IBOutlet var feedbackLabel: NSTextField!
private func setup() { private func setup() {
feedbackLabel.stringValue = "Feedback is always welcome:" feedbackLabel.stringValue = "Feedback is always welcome:"

2
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

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

57
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -8,10 +8,10 @@ class AppearanceViewController: ParentViewController {
@IBOutlet var informationLabel: NSTextField! @IBOutlet var informationLabel: NSTextField!
@IBOutlet var sliderDayRangePopup: NSPopUpButton! @IBOutlet var sliderDayRangePopup: NSPopUpButton!
@IBOutlet var visualEffectView: NSVisualEffectView! @IBOutlet var visualEffectView: NSVisualEffectView!
@IBOutlet weak var menubarMode: NSSegmentedControl! @IBOutlet var menubarMode: NSSegmentedControl!
@IBOutlet weak var includeDayInMenubarControl: NSSegmentedControl! @IBOutlet var includeDayInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includeDateInMenubarControl: NSSegmentedControl! @IBOutlet var includeDateInMenubarControl: NSSegmentedControl!
@IBOutlet weak var includePlaceNameControl: NSSegmentedControl! @IBOutlet var includePlaceNameControl: NSSegmentedControl!
private var themeDidChangeNotification: NSObjectProtocol? private var themeDidChangeNotification: NSObjectProtocol?
@ -31,12 +31,12 @@ class AppearanceViewController: ParentViewController {
"4 days", "4 days",
"5 days", "5 days",
"6 days", "6 days",
"7 days" "7 days",
]) ])
setup() setup()
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.setup() self.setup()
self.animateBackgroundColorChange() self.animateBackgroundColorChange()
self.view.needsDisplay = true // Let's make the color change permanent. self.view.needsDisplay = true // Let's make the color change permanent.
@ -54,7 +54,7 @@ class AppearanceViewController: ParentViewController {
colorAnimation.duration = 0.25 colorAnimation.duration = 0.25
colorAnimation.fromValue = previousBackgroundColor.cgColor colorAnimation.fromValue = previousBackgroundColor.cgColor
colorAnimation.toValue = Themer.shared().mainBackgroundColor().cgColor colorAnimation.toValue = Themer.shared().mainBackgroundColor().cgColor
self.view.layer?.add(colorAnimation, forKey: "backgroundColor") view.layer?.add(colorAnimation, forKey: "backgroundColor")
} }
override func viewWillAppear() { override func viewWillAppear() {
@ -80,21 +80,21 @@ class AppearanceViewController: ParentViewController {
updateMenubarControls(!shouldDisplayCompact) updateMenubarControls(!shouldDisplayCompact)
} }
@IBOutlet weak var headerLabel: NSTextField! @IBOutlet var headerLabel: NSTextField!
@IBOutlet weak var timeFormatLabel: NSTextField! @IBOutlet var timeFormatLabel: NSTextField!
@IBOutlet weak var panelTheme: NSTextField! @IBOutlet var panelTheme: NSTextField!
@IBOutlet weak var dayDisplayOptionsLabel: NSTextField! @IBOutlet var dayDisplayOptionsLabel: NSTextField!
@IBOutlet weak var showSliderLabel: NSTextField! @IBOutlet var showSliderLabel: NSTextField!
@IBOutlet weak var showSunriseLabel: NSTextField! @IBOutlet var showSunriseLabel: NSTextField!
@IBOutlet weak var showSecondsLabel: NSTextField! @IBOutlet var showSecondsLabel: NSTextField!
@IBOutlet weak var largerTextLabel: NSTextField! @IBOutlet var largerTextLabel: NSTextField!
@IBOutlet weak var futureSliderRangeLabel: NSTextField! @IBOutlet var futureSliderRangeLabel: NSTextField!
@IBOutlet weak var includeDateLabel: NSTextField! @IBOutlet var includeDateLabel: NSTextField!
@IBOutlet weak var includeDayLabel: NSTextField! @IBOutlet var includeDayLabel: NSTextField!
@IBOutlet weak var includePlaceLabel: NSTextField! @IBOutlet var includePlaceLabel: NSTextField!
@IBOutlet weak var menubarDisplayOptionsLabel: NSTextField! @IBOutlet var menubarDisplayOptionsLabel: NSTextField!
@IBOutlet weak var appDisplayLabel: NSTextField! @IBOutlet var appDisplayLabel: NSTextField!
@IBOutlet weak var menubarModeLabel: NSTextField! @IBOutlet var menubarModeLabel: NSTextField!
private func setup() { private func setup() {
headerLabel.stringValue = "Main Panel Options" headerLabel.stringValue = "Main Panel Options"
@ -136,7 +136,6 @@ class AppearanceViewController: ParentViewController {
private var previousBackgroundColor: NSColor = NSColor.white private var previousBackgroundColor: NSColor = NSColor.white
@IBAction func themeChanged(_ sender: NSSegmentedControl) { @IBAction func themeChanged(_ sender: NSSegmentedControl) {
previousBackgroundColor = Themer.shared().mainBackgroundColor() previousBackgroundColor = Themer.shared().mainBackgroundColor()
Themer.shared().set(theme: sender.selectedSegment) Themer.shared().set(theme: sender.selectedSegment)
@ -208,7 +207,6 @@ class AppearanceViewController: ParentViewController {
} }
@IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) { @IBAction func changeAppDisplayOptions(_ sender: NSSegmentedControl) {
if sender.selectedSegment == 0 { if sender.selectedSegment == 0 {
Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode") Logger.log(object: ["Selection": "Menubar"], for: "Dock Mode")
NSApp.setActivationPolicy(.accessory) NSApp.setActivationPolicy(.accessory)
@ -220,7 +218,7 @@ class AppearanceViewController: ParentViewController {
private func refresh(panel: Bool, floating: Bool) { private func refresh(panel: Bool, floating: Bool) {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if panel && DataStore.shared().shouldDisplay(ViewType.showAppInForeground) == false { if panel, DataStore.shared().shouldDisplay(ViewType.showAppInForeground) == false {
guard let panelController = PanelController.panel() else { return } guard let panelController = PanelController.panel() else { return }
let futureSliderBounds = panelController.futureSlider.bounds let futureSliderBounds = panelController.futureSlider.bounds
@ -232,7 +230,7 @@ class AppearanceViewController: ParentViewController {
panelController.setupMenubarTimer() panelController.setupMenubarTimer()
} }
if floating && DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { if floating, DataStore.shared().shouldDisplay(ViewType.showAppInForeground) {
if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) { if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) {
let floatingWindow = FloatingWindowController.shared() let floatingWindow = FloatingWindowController.shared()
floatingWindow.updateTableContent() floatingWindow.updateTableContent()
@ -246,16 +244,16 @@ class AppearanceViewController: ParentViewController {
} }
} }
@IBAction func displayDayInMenubarAction(_ sender: Any) { @IBAction func displayDayInMenubarAction(_: Any) {
DataStore.shared().updateDayPreference() DataStore.shared().updateDayPreference()
updateStatusItem() updateStatusItem()
} }
@IBAction func displayDateInMenubarAction(_ sender: Any) { @IBAction func displayDateInMenubarAction(_: Any) {
updateStatusItem() updateStatusItem()
} }
@IBAction func displayPlaceInMenubarAction(_ sender: Any) { @IBAction func displayPlaceInMenubarAction(_: Any) {
updateStatusItem() updateStatusItem()
} }
@ -285,7 +283,6 @@ class AppearanceViewController: ParentViewController {
} else { } else {
Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Standard Mode") Logger.log(object: ["Context": "In Appearance View"], for: "Switched to Standard Mode")
} }
} }
// We don't support showing day or date in the menubar for compact mode yet. // We don't support showing day or date in the menubar for compact mode yet.

70
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -4,7 +4,6 @@ import Cocoa
import EventKit import EventKit
class ClockerTextBackgroundView: NSView { class ClockerTextBackgroundView: NSView {
private var themeDidChangeNotification: NSObjectProtocol? private var themeDidChangeNotification: NSObjectProtocol?
override func awakeFromNib() { override func awakeFromNib() {
@ -13,7 +12,7 @@ class ClockerTextBackgroundView: NSView {
layer?.masksToBounds = false layer?.masksToBounds = false
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
} }
} }
@ -31,18 +30,17 @@ class ClockerTextBackgroundView: NSView {
} }
class CalendarViewController: ParentViewController { class CalendarViewController: ParentViewController {
@IBOutlet var showSegmentedControl: NSSegmentedControl! @IBOutlet var showSegmentedControl: NSSegmentedControl!
@IBOutlet var allDaysSegmentedControl: NSSegmentedControl! @IBOutlet var allDaysSegmentedControl: NSSegmentedControl!
@IBOutlet var truncateTextField: NSTextField! @IBOutlet var truncateTextField: NSTextField!
@IBOutlet var noAccessView: NSVisualEffectView! @IBOutlet var noAccessView: NSVisualEffectView!
@IBOutlet var informationField: NSTextField! @IBOutlet var informationField: NSTextField!
@IBOutlet var grantAccessButton: NSButton! @IBOutlet var grantAccessButton: NSButton!
@IBOutlet weak var calendarsTableView: NSTableView! @IBOutlet var calendarsTableView: NSTableView!
@IBOutlet weak var showNextMeetingInMenubarControl: NSSegmentedControl! @IBOutlet var showNextMeetingInMenubarControl: NSSegmentedControl!
@IBOutlet weak var backgroundView: NSView! @IBOutlet var backgroundView: NSView!
@IBOutlet weak var nextMeetingBackgroundView: NSView! @IBOutlet var nextMeetingBackgroundView: NSView!
private var themeDidChangeNotification: NSObjectProtocol? private var themeDidChangeNotification: NSObjectProtocol?
private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars() private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars()
@ -57,7 +55,7 @@ class CalendarViewController: ParentViewController {
name: .calendarAccessGranted, name: .calendarAccessGranted,
object: nil) object: nil)
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.setup() self.setup()
} }
@ -91,7 +89,7 @@ class CalendarViewController: ParentViewController {
} }
// If the menubar mode is compact, we can't show meetings in the menubar. So disable toggling that option. // If the menubar mode is compact, we can't show meetings in the menubar. So disable toggling that option.
showNextMeetingInMenubarControl.isEnabled = !(DataStore.shared().shouldDisplay(.menubarCompactMode)) showNextMeetingInMenubarControl.isEnabled = !DataStore.shared().shouldDisplay(.menubarCompactMode)
} }
private func verifyCalendarAccess() { private func verifyCalendarAccess() {
@ -102,7 +100,7 @@ class CalendarViewController: ParentViewController {
noAccessView.isHidden = hasCalendarAccess noAccessView.isHidden = hasCalendarAccess
if hasNotDeterminedCalendarAccess { if hasNotDeterminedCalendarAccess {
informationField.stringValue = "Clocker is more useful when it can display events from your calendars." informationField.stringValue = "Clocker is more useful when it can display events from your calendars."
setGrantAccess(title: "Grant Access") setGrantAccess(title: "Grant Access")
} else if hasDeniedCalendarAccess { } else if hasDeniedCalendarAccess {
// The informationField text is taken care off in the XIB. Just set the grant button to empty because we can't do anything. // The informationField text is taken care off in the XIB. Just set the grant button to empty because we can't do anything.
@ -121,7 +119,7 @@ class CalendarViewController: ParentViewController {
let attributesDictionary: [NSAttributedString.Key: Any] = [ let attributesDictionary: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.paragraphStyle: style,
NSAttributedString.Key.font: boldFont, NSAttributedString.Key.font: boldFont,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor() NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
] ]
let attributedString = NSAttributedString(string: title, let attributedString = NSAttributedString(string: title,
attributes: attributesDictionary) attributes: attributesDictionary)
@ -146,7 +144,6 @@ class CalendarViewController: ParentViewController {
} }
@IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) { @IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) {
// We need to start the menubar timer if it hasn't been started already // We need to start the menubar timer if it hasn't been started already
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { guard let delegate = NSApplication.shared.delegate as? AppDelegate else {
assertionFailure() assertionFailure()
@ -156,7 +153,6 @@ class CalendarViewController: ParentViewController {
let statusItemHandler = delegate.statusItemForPanel() let statusItemHandler = delegate.statusItemForPanel()
if sender.selectedSegment == 0 { if sender.selectedSegment == 0 {
if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true { if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true {
print("Timer is already in progress") print("Timer is already in progress")
updateStatusItem() updateStatusItem()
@ -164,10 +160,8 @@ class CalendarViewController: ParentViewController {
} }
} else { } else {
statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false) statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false)
} }
} }
@IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) { @IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) {
@ -203,26 +197,26 @@ class CalendarViewController: ParentViewController {
statusItem.performTimerWork() statusItem.performTimerWork()
} }
@IBOutlet weak var headerLabel: NSTextField! @IBOutlet var headerLabel: NSTextField!
@IBOutlet weak var upcomingEventView: NSTextField! @IBOutlet var upcomingEventView: NSTextField!
@IBOutlet weak var allDayMeetingsLabel: NSTextField! @IBOutlet var allDayMeetingsLabel: NSTextField!
@IBOutlet weak var showNextMeetingLabel: NSTextField! @IBOutlet var showNextMeetingLabel: NSTextField!
@IBOutlet weak var nextMeetingAccessoryLabel: NSTextField! @IBOutlet var nextMeetingAccessoryLabel: NSTextField!
@IBOutlet weak var truncateTextLabel: NSTextField! @IBOutlet var truncateTextLabel: NSTextField!
@IBOutlet weak var showEventsFromLabel: NSTextField! @IBOutlet var showEventsFromLabel: NSTextField!
@IBOutlet weak var charactersField: NSTextField! @IBOutlet var charactersField: NSTextField!
@IBOutlet weak var truncateAccessoryLabel: NSTextField! @IBOutlet var truncateAccessoryLabel: NSTextField!
private func setup() { private func setup() {
// Grant access button's text color is taken care above. // Grant access button's text color is taken care above.
headerLabel.stringValue = "Upcoming Event View Options" headerLabel.stringValue = "Upcoming Event View Options"
upcomingEventView.stringValue = "Show Upcoming Event View" upcomingEventView.stringValue = "Show Upcoming Event View"
allDayMeetingsLabel.stringValue = "Show All Day Meetings" allDayMeetingsLabel.stringValue = "Show All Day Meetings"
showNextMeetingLabel.stringValue = "Show Next Meeting Title in Menubar" showNextMeetingLabel.stringValue = "Show Next Meeting Title in Menubar"
truncateTextLabel.stringValue = "Truncate menubar text longer than" truncateTextLabel.stringValue = "Truncate menubar text longer than"
charactersField.stringValue = "characters" charactersField.stringValue = "characters"
showEventsFromLabel.stringValue = "Show events from" showEventsFromLabel.stringValue = "Show events from"
truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"" truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""
[headerLabel, upcomingEventView, allDayMeetingsLabel, [headerLabel, upcomingEventView, allDayMeetingsLabel,
showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel, showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel,
@ -231,21 +225,18 @@ class CalendarViewController: ParentViewController {
} }
extension CalendarViewController: NSTableViewDataSource { extension CalendarViewController: NSTableViewDataSource {
func numberOfRows(in _: NSTableView) -> Int {
func numberOfRows(in tableView: NSTableView) -> Int {
let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted() let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted()
return hasCalendarAccess ? calendars.count : 0 return hasCalendarAccess ? calendars.count : 0
} }
} }
extension CalendarViewController: NSTableViewDelegate { extension CalendarViewController: NSTableViewDelegate {
func tableView(_: NSTableView, shouldSelectRow _: Int) -> Bool {
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return false return false
} }
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { func tableView(_: NSTableView, heightOfRow row: Int) -> CGFloat {
guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else { guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else {
return 30.0 return 30.0
} }
@ -253,8 +244,7 @@ extension CalendarViewController: NSTableViewDelegate {
return 24.0 return 24.0
} }
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? {
if let currentSource = calendars[row] as? String, if let currentSource = calendars[row] as? String,
let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell { let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell {
message.sourceName.stringValue = currentSource message.sourceName.stringValue = currentSource
@ -273,7 +263,6 @@ extension CalendarViewController: NSTableViewDelegate {
} }
return nil return nil
} }
@objc func calendarSelected(_ checkbox: NSButton) { @objc func calendarSelected(_ checkbox: NSButton) {
@ -290,7 +279,6 @@ extension CalendarViewController: NSTableViewDelegate {
} }
private func updateSelectedCalendars(_ selection: [Any]) { private func updateSelectedCalendars(_ selection: [Any]) {
var selectedCalendars: [String] = [] var selectedCalendars: [String] = []
for obj in selection { for obj in selection {

56
Clocker/Preferences/General/PreferencesViewController.swift

@ -70,7 +70,7 @@ class PreferencesViewController: ParentViewController {
@IBOutlet private var headerView: NSView! @IBOutlet private var headerView: NSView!
@IBOutlet private var tableview: NSView! @IBOutlet private var tableview: NSView!
@IBOutlet private var additionalSortOptions: NSView! @IBOutlet private var additionalSortOptions: NSView!
@IBOutlet weak var startAtLoginLabel: NSTextField! @IBOutlet var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton! @IBOutlet var startupCheckbox: NSButton!
@IBOutlet var headerLabel: NSTextField! @IBOutlet var headerLabel: NSTextField!
@ -198,7 +198,7 @@ class PreferencesViewController: ParentViewController {
} }
let archivedObjects = menubarTimes.map { (timezone) -> Data in let archivedObjects = menubarTimes.map { (timezone) -> Data in
return NSKeyedArchiver.archivedData(withRootObject: timezone) NSKeyedArchiver.archivedData(withRootObject: timezone)
} }
UserDefaults.standard.set(archivedObjects, forKey: CLMenubarFavorites) UserDefaults.standard.set(archivedObjects, forKey: CLMenubarFavorites)
@ -244,7 +244,7 @@ class PreferencesViewController: ParentViewController {
[timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach { [timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach {
$0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [ $0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(), NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)! NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
]) ])
} }
@ -362,7 +362,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
return nil return nil
} }
private func handleTimezoneNameIdentifier(for row: Int, _ selectedDataSource: TimezoneData?) -> Any? { private func handleTimezoneNameIdentifier(for _: Int, _ selectedDataSource: TimezoneData?) -> Any? {
guard let model = selectedDataSource else { guard let model = selectedDataSource else {
return nil return nil
} }
@ -382,7 +382,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
return dataSource?.formattedAddress return dataSource?.formattedAddress
} }
} else { } else {
if searchField.stringValue.isEmpty == false && row < timezoneFilteredArray.count { if searchField.stringValue.isEmpty == false, row < timezoneFilteredArray.count {
return timezoneFilteredArray[row] return timezoneFilteredArray[row]
} }
return timezoneArray[row] return timezoneArray[row]
@ -391,7 +391,7 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
} }
private func handleAbbreviationColumn(for row: Int) -> Any? { private func handleAbbreviationColumn(for row: Int) -> Any? {
if searchField.stringValue.isEmpty == false && (row < timezoneFilteredArray.count) { if searchField.stringValue.isEmpty == false, row < timezoneFilteredArray.count {
let currentSelection = timezoneFilteredArray[row] let currentSelection = timezoneFilteredArray[row]
if currentSelection == "UTC" { if currentSelection == "UTC" {
return "UTC" return "UTC"
@ -497,8 +497,8 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if selectedTimeZones.count > row { if selectedTimeZones.count > row {
Logger.log(object: [ Logger.log(object: [
"Old Label": dataObject.customLabel ?? "Error", "Old Label": dataObject.customLabel ?? "Error",
"New Label": formattedValue "New Label": formattedValue,
], ],
for: "Custom Label Changed") for: "Custom Label Changed")
dataObject.setLabel(formattedValue) dataObject.setLabel(formattedValue)
@ -510,25 +510,24 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
Logger.log(object: [ Logger.log(object: [
"MethodName": "SetObjectValue", "MethodName": "SetObjectValue",
"Selected Timezone Count": selectedTimeZones.count, "Selected Timezone Count": selectedTimeZones.count,
"Current Row": row "Current Row": row,
], ],
for: "Error in selected row count") for: "Error in selected row count")
} }
} }
private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() { private func showAlertIfMoreThanOneTimezoneHasBeenAddedToTheMenubar() {
let isUITestRunning = ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) let isUITestRunning = ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument)
// If we have seen displayed the message before, abort! // If we have seen displayed the message before, abort!
let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: CLLongStatusBarWarningMessage) let haveWeSeenThisMessageBefore = UserDefaults.standard.bool(forKey: CLLongStatusBarWarningMessage)
if haveWeSeenThisMessageBefore && !isUITestRunning { if haveWeSeenThisMessageBefore, !isUITestRunning {
return return
} }
// If the user is already using the compact mode, abort. // If the user is already using the compact mode, abort.
if DataStore.shared().shouldDisplay(.menubarCompactMode) && !isUITestRunning { if DataStore.shared().shouldDisplay(.menubarCompactMode), !isUITestRunning {
return return
} }
@ -692,7 +691,7 @@ extension PreferencesViewController {
self.dataTask = NetworkManager.task(with: self.generateSearchURL(), self.dataTask = NetworkManager.task(with: self.generateSearchURL(),
completionHandler: { [weak self] response, error in completionHandler: { [weak self] response, error in
guard let `self` = self else { return } guard let self = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if let errorPresent = error { if let errorPresent = error {
@ -734,12 +733,12 @@ extension PreferencesViewController {
private func presentError(_ errorMessage: String) { private func presentError(_ errorMessage: String) {
if errorMessage == PreferencesConstants.offlineErrorMessage { if errorMessage == PreferencesConstants.offlineErrorMessage {
self.placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else { } else {
self.placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
} }
self.isActivityInProgress = false isActivityInProgress = false
} }
private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) { private func appendResultsToFilteredArray(_ results: [SearchResult.Result]) {
@ -755,17 +754,17 @@ extension PreferencesViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: $0.placeId CLPlaceIdentifier: $0.placeId,
] as [String: Any] ] as [String: Any]
self.filteredArray.append(TimezoneData(with: totalPackage)) self.filteredArray.append(TimezoneData(with: totalPackage))
} }
} }
private func prepareUIForPresentingResults() { private func prepareUIForPresentingResults() {
self.placeholderLabel.placeholderString = CLEmptyString placeholderLabel.placeholderString = CLEmptyString
self.isActivityInProgress = false isActivityInProgress = false
self.availableTimezoneTableView.reloadData() availableTimezoneTableView.reloadData()
} }
// Extracting this out for tests // Extracting this out for tests
@ -821,7 +820,7 @@ extension PreferencesViewController {
NetworkManager.task(with: urlString) { [weak self] response, error in NetworkManager.task(with: urlString) { [weak self] response, error in
guard let `self` = self else { return } guard let self = self else { return }
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if self.handleEdgeCase(for: response) == true { if self.handleEdgeCase(for: response) == true {
@ -829,7 +828,7 @@ extension PreferencesViewController {
} }
if error == nil, let json = response, let timezone = self.decodeTimezone(from: json) { if error == nil, let json = response, let timezone = self.decodeTimezone(from: json) {
if self.availableTimezoneTableView.selectedRow >= 0 && self.availableTimezoneTableView.selectedRow < self.filteredArray.count { if self.availableTimezoneTableView.selectedRow >= 0, self.availableTimezoneTableView.selectedRow < self.filteredArray.count {
self.installTimezone(timezone) self.installTimezone(timezone)
} }
self.updateViewState() self.updateViewState()
@ -867,8 +866,8 @@ extension PreferencesViewController {
"latitude": dataObject.latitude!, "latitude": dataObject.latitude!,
"longitude": dataObject.longitude!, "longitude": dataObject.longitude!,
"nextUpdate": CLEmptyString, "nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress CLCustomLabel: filteredAddress,
] as [String: Any] ] as [String: Any]
let timezoneObject = TimezoneData(with: newTimeZone) let timezoneObject = TimezoneData(with: newTimeZone)
let operationsObject = TimezoneDataOperations(with: timezoneObject) let operationsObject = TimezoneDataOperations(with: timezoneObject)
@ -1118,7 +1117,7 @@ extension PreferencesViewController {
var newDefaults = selectedTimeZones var newDefaults = selectedTimeZones
let objectsToRemove = timezoneTableView.selectedRowIndexes.map { (index) -> Data in let objectsToRemove = timezoneTableView.selectedRowIndexes.map { (index) -> Data in
return selectedTimeZones[index] selectedTimeZones[index]
} }
newDefaults = newDefaults.filter { !objectsToRemove.contains($0) } newDefaults = newDefaults.filter { !objectsToRemove.contains($0) }
@ -1331,8 +1330,7 @@ extension PreferencesViewController {
} }
} }
extension PreferencesViewController: SRRecorderControlDelegate { extension PreferencesViewController: SRRecorderControlDelegate {}
}
// Helpers // Helpers
extension PreferencesViewController { extension PreferencesViewController {

20
Clocker/Preferences/OneWindowController.swift

@ -3,15 +3,14 @@
import Cocoa import Cocoa
class CenteredTabViewController: NSTabViewController { class CenteredTabViewController: NSTabViewController {
override func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { override func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
super.toolbarDefaultItemIdentifiers(toolbar) super.toolbarDefaultItemIdentifiers(toolbar)
var toolbarItems: [NSToolbarItem.Identifier] = [NSToolbarItem.Identifier.flexibleSpace] var toolbarItems: [NSToolbarItem.Identifier] = [NSToolbarItem.Identifier.flexibleSpace]
tabViewItems.forEach { (item) in tabViewItems.forEach { item in
if let identifier = item.identifier as? String { if let identifier = item.identifier as? String {
toolbarItems.append(NSToolbarItem.Identifier.init(identifier)) toolbarItems.append(NSToolbarItem.Identifier(identifier))
} }
} }
@ -19,11 +18,9 @@ class CenteredTabViewController: NSTabViewController {
return toolbarItems return toolbarItems
} }
} }
class OneWindowController: NSWindowController { class OneWindowController: NSWindowController {
private static var sharedWindow: OneWindowController! private static var sharedWindow: OneWindowController!
private var themeDidChangeNotification: NSObjectProtocol? private var themeDidChangeNotification: NSObjectProtocol?
@ -31,13 +28,13 @@ class OneWindowController: NSWindowController {
super.windowDidLoad() super.windowDidLoad()
setup() setup()
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (_) in themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
NSAnimationContext.runAnimationGroup({ (context) in NSAnimationContext.runAnimationGroup { context in
context.duration = 1 context.duration = 1
context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
self.window?.animator().backgroundColor = Themer.shared().mainBackgroundColor() self.window?.animator().backgroundColor = Themer.shared().mainBackgroundColor()
}) }
self.setupToolbarImages() self.setupToolbarImages()
} }
@ -61,7 +58,7 @@ class OneWindowController: NSWindowController {
class func shared() -> OneWindowController { class func shared() -> OneWindowController {
if sharedWindow == nil { if sharedWindow == nil {
let prefStoryboard = NSStoryboard.init(name: "Preferences", bundle: nil) let prefStoryboard = NSStoryboard(name: "Preferences", bundle: nil)
sharedWindow = prefStoryboard.instantiateInitialController() as? OneWindowController sharedWindow = prefStoryboard.instantiateInitialController() as? OneWindowController
} }
return sharedWindow return sharedWindow
@ -72,7 +69,7 @@ class OneWindowController: NSWindowController {
return return
} }
if !(window.isMainWindow) || !(window.isVisible) { if !window.isMainWindow || !window.isVisible {
showWindow(nil) showWindow(nil)
} }
@ -93,12 +90,11 @@ class OneWindowController: NSWindowController {
"Calendar": themer.calendarTabImage(), "Calendar": themer.calendarTabImage(),
"Permissions": themer.privacyTabImage()] "Permissions": themer.privacyTabImage()]
tabViewController.tabViewItems.forEach { (tabViewItem) in tabViewController.tabViewItems.forEach { tabViewItem in
let identity = (tabViewItem.identifier as? String) ?? "" let identity = (tabViewItem.identifier as? String) ?? ""
if identifierTOImageMapping[identity] != nil { if identifierTOImageMapping[identity] != nil {
tabViewItem.image = identifierTOImageMapping[identity] tabViewItem.image = identifierTOImageMapping[identity]
} }
} }
} }
} }

3
Clocker/Preferences/Permissions/PermissionsViewController.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
class PermissionsViewController: ParentViewController { class PermissionsViewController: ParentViewController {
@IBOutlet var calendarContainerView: NSView! @IBOutlet var calendarContainerView: NSView!
@IBOutlet var remindersContainerView: NSView! @IBOutlet var remindersContainerView: NSView!
@ -129,7 +128,6 @@ class PermissionsViewController: ParentViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
@ -163,7 +161,6 @@ class PermissionsViewController: ParentViewController {
if granted { if granted {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.view.window?.orderBack(nil) self.view.window?.orderBack(nil)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)

Loading…
Cancel
Save