Browse Source

Merge branch 'master' of https://github.com/n0shake/Clocker

pull/113/head
Abhishek Banthia 3 years ago
parent
commit
5c9f914140
  1. 6
      Clocker/AppDelegate.swift
  2. 16
      Clocker/Clocker.xcodeproj/project.pbxproj
  3. 73
      Clocker/ClockerUnitTests/AppDelegateTests.swift
  4. 51
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  5. 36
      Clocker/ClockerUnitTests/DateFormatterManagerTests.swift
  6. 4
      Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift
  7. 44
      Clocker/ClockerUnitTests/ThemerTests.swift
  8. 6
      Clocker/CoreLoggerKit/Package.swift
  9. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/CoreLoggerKitTests.swift
  10. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/XCTestManifests.swift
  11. 8
      Clocker/CoreModelKit/Package.swift
  12. 10
      Clocker/CoreModelKit/Sources/CoreModelKit/SearchResults.swift
  13. 8
      Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift
  14. 2
      Clocker/CoreModelKit/Tests/CoreModelKitTests/XCTestManifests.swift
  15. 6
      Clocker/Dependencies/Date Additions/TimePeriodChain.swift
  16. 4
      Clocker/Events and Reminders/CalendarHandler.swift
  17. 4
      Clocker/Menu Bar/StatusContainerView.swift
  18. 2
      Clocker/Menu Bar/StatusItemHandler.swift
  19. 8
      Clocker/Menu Bar/StatusItemView.swift
  20. 2
      Clocker/Onboarding/FinalOnboardingViewController.swift
  21. 12
      Clocker/Onboarding/OnboardingSearchController.swift
  22. 4
      Clocker/Overall App/ConfigExport.swift
  23. 12
      Clocker/Overall App/DataStore.swift
  24. 25
      Clocker/Overall App/DateFormatterManager.swift
  25. 2
      Clocker/Overall App/NetworkManager.swift
  26. 60
      Clocker/Overall App/Themer.swift
  27. 2
      Clocker/Overall App/Timer.swift
  28. 10
      Clocker/Overall App/VersionUpdateHandler.swift
  29. 30
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  30. 6
      Clocker/Panel/Notes Popover/NotesPopover.swift
  31. 2
      Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift
  32. 24
      Clocker/Panel/PanelController.swift
  33. 2
      Clocker/Panel/ParentPanelController+ModernSlider.swift
  34. 53
      Clocker/Panel/ParentPanelController.swift
  35. 4
      Clocker/Panel/Rate Controller/ReviewController.swift
  36. 2
      Clocker/Panel/UI/PanelTableView.swift
  37. 6
      Clocker/Panel/UI/TimezoneCellView.swift
  38. 2
      Clocker/Panel/UI/TimezoneDataSource.swift
  39. 6
      Clocker/Preferences/About/AboutViewController.swift
  40. 14
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  41. 4
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  42. 7
      Clocker/Preferences/Calendar/CalendarViewController.swift
  43. 10
      Clocker/Preferences/General/PreferencesDataSource.swift
  44. 41
      Clocker/Preferences/General/PreferencesViewController.swift
  45. 5
      Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift
  46. 68
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  47. 52
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift
  48. 26
      Clocker/Preferences/Menu Bar/StatusItemView.swift
  49. 28
      Clocker/Preferences/Menu Bar/UpcomingEventStatusItemView.swift
  50. 8
      Clocker/Preferences/OneWindowController.swift
  51. 6
      Clocker/StartupKit/Package.swift

6
Clocker/AppDelegate.swift

@ -16,7 +16,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
panelObserver?.invalidate() panelObserver?.invalidate()
} }
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
if let path = keyPath, path == PreferencesConstants.hotKeyPathIdentifier { if let path = keyPath, path == PreferencesConstants.hotKeyPathIdentifier {
let hotKeyCenter = PTHotKeyCenter.shared() let hotKeyCenter = PTHotKeyCenter.shared()
@ -262,10 +262,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
return statusBarHandler return statusBarHandler
} }
open func setPanelDefaults() {
panelController.updateDefaultPreferences()
}
open func setupMenubarTimer() { open func setupMenubarTimer() {
statusBarHandler.setupStatusItem() statusBarHandler.setupStatusItem()
} }

16
Clocker/Clocker.xcodeproj/project.pbxproj

@ -7,7 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
3508CC942599FFEC000E3530 /* MenubarHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC932599FFEC000E3530 /* MenubarHandler.swift */; }; 3508CC942599FFEC000E3530 /* MenubarTitleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */; };
3508CC9A259A0001000E3530 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC99259A0001000E3530 /* StatusItemView.swift */; }; 3508CC9A259A0001000E3530 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC99259A0001000E3530 /* StatusItemView.swift */; };
3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */; }; 3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */; };
3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CCA9259A0027000E3530 /* StatusContainerView.swift */; }; 3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3508CCA9259A0027000E3530 /* StatusContainerView.swift */; };
@ -51,6 +51,8 @@
3548C45F26BEEFB400AFB533 /* UpcomingEventsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C45E26BEEFB400AFB533 /* UpcomingEventsDataSource.swift */; }; 3548C45F26BEEFB400AFB533 /* UpcomingEventsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C45E26BEEFB400AFB533 /* UpcomingEventsDataSource.swift */; };
3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C46026BEEFE400AFB533 /* UpcomingEventViewItem.swift */; }; 3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3548C46026BEEFE400AFB533 /* UpcomingEventViewItem.swift */; };
35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */; }; 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */; };
35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */; };
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */; };
357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* TimeMarkerViewItem.swift */; }; 357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* TimeMarkerViewItem.swift */; };
357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357391862507277500D30819 /* HourMarkerViewItem.xib */; }; 357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357391862507277500D30819 /* HourMarkerViewItem.xib */; };
3579765E2680208C009DDA6E /* ParentPanelController+ModernSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */; }; 3579765E2680208C009DDA6E /* ParentPanelController+ModernSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */; };
@ -255,7 +257,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
3508CC932599FFEC000E3530 /* MenubarHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenubarHandler.swift; sourceTree = "<group>"; }; 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenubarTitleProvider.swift; sourceTree = "<group>"; };
3508CC99259A0001000E3530 /* StatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; }; 3508CC99259A0001000E3530 /* StatusItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
3508CC9E259A000E000E3530 /* StatusItemHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemHandler.swift; sourceTree = "<group>"; }; 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemHandler.swift; sourceTree = "<group>"; };
3508CCA9259A0027000E3530 /* StatusContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = "<group>"; }; 3508CCA9259A0027000E3530 /* StatusContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = "<group>"; };
@ -286,6 +288,8 @@
3552066427AF6488000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3552066427AF6488000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3552066527AF6489000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; }; 3552066527AF6489000EF08F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
35584D1327EF8EB5006E3EAD /* ThemerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemerTests.swift; sourceTree = "<group>"; }; 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemerTests.swift; sourceTree = "<group>"; };
35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatterManagerTests.swift; sourceTree = "<group>"; };
35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateTests.swift; sourceTree = "<group>"; };
3569A44E25441F320087E254 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; }; 3569A44E25441F320087E254 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
357391852507277500D30819 /* TimeMarkerViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeMarkerViewItem.swift; sourceTree = "<group>"; }; 357391852507277500D30819 /* TimeMarkerViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeMarkerViewItem.swift; sourceTree = "<group>"; };
357391862507277500D30819 /* HourMarkerViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HourMarkerViewItem.xib; sourceTree = "<group>"; }; 357391862507277500D30819 /* HourMarkerViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HourMarkerViewItem.xib; sourceTree = "<group>"; };
@ -568,7 +572,7 @@
35C36EDF22595D9C002FA5C6 /* Menu Bar */ = { 35C36EDF22595D9C002FA5C6 /* Menu Bar */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3508CC932599FFEC000E3530 /* MenubarHandler.swift */, 3508CC932599FFEC000E3530 /* MenubarTitleProvider.swift */,
3508CC99259A0001000E3530 /* StatusItemView.swift */, 3508CC99259A0001000E3530 /* StatusItemView.swift */,
3508CC9E259A000E000E3530 /* StatusItemHandler.swift */, 3508CC9E259A000E000E3530 /* StatusItemHandler.swift */,
3508CCA9259A0027000E3530 /* StatusContainerView.swift */, 3508CCA9259A0027000E3530 /* StatusContainerView.swift */,
@ -881,6 +885,8 @@
C20839CB21515C1F00C86589 /* Info.plist */, C20839CB21515C1F00C86589 /* Info.plist */,
9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */, 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */,
35584D1327EF8EB5006E3EAD /* ThemerTests.swift */, 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */,
35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */,
35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */,
); );
path = ClockerUnitTests; path = ClockerUnitTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1267,7 +1273,9 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */, 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */,
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */,
9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */, 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */,
35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */,
C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */, C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -1317,7 +1325,7 @@
35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */, 35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */,
35C36F5D2259DD96002FA5C6 /* TimezoneDataOperations.swift in Sources */, 35C36F5D2259DD96002FA5C6 /* TimezoneDataOperations.swift in Sources */,
3548C45D26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift in Sources */, 3548C45D26BEEF4C00AFB533 /* ParentPanelController+UpcomingEvents.swift in Sources */,
3508CC942599FFEC000E3530 /* MenubarHandler.swift in Sources */, 3508CC942599FFEC000E3530 /* MenubarTitleProvider.swift in Sources */,
35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */, 35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */,
35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */, 35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */,
35C36F622259DE67002FA5C6 /* NotesPopover.swift in Sources */, 35C36F622259DE67002FA5C6 /* NotesPopover.swift in Sources */,

73
Clocker/ClockerUnitTests/AppDelegateTests.swift

@ -0,0 +1,73 @@
// Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker
class AppDelegateTests: XCTestCase {
func testStatusItemIsInitialized() throws {
let subject = NSApplication.shared.delegate as? AppDelegate
let statusHandler = subject?.statusItemForPanel()
XCTAssertNotNil(EventCenter.sharedCenter)
XCTAssertNotNil(statusHandler)
}
func testDockMenu() throws {
let subject = NSApplication.shared.delegate as? AppDelegate
let dockMenu = subject?.applicationDockMenu(NSApplication.shared)
let items = dockMenu?.items ?? []
XCTAssertEqual(dockMenu?.title, "Quick Access")
XCTAssertEqual(items.first?.title, "Toggle Panel")
XCTAssertEqual(items[1].title, "Preferences")
XCTAssertEqual(items[1].keyEquivalent, ",")
XCTAssertEqual(items[2].title, "Hide from Dock")
// Test selections
XCTAssertEqual(items.first?.action, #selector(AppDelegate.togglePanel(_:)))
XCTAssertEqual(items[2].action, #selector(AppDelegate.hideFromDock))
items.forEach { menuItem in
XCTAssertTrue(menuItem.isEnabled)
}
}
func testSetupMenubarTimer() {
let subject = NSApplication.shared.delegate as? AppDelegate
let statusItemHandler = subject?.statusItemForPanel()
XCTAssertEqual(statusItemHandler?.statusItem.autosaveName, NSStatusItem.AutosaveName("ClockerStatusItem"))
}
func testFloatingWindow() {
let subject = NSApplication.shared.delegate as? AppDelegate
let previousWindows = NSApplication.shared.windows
XCTAssertEqual(previousWindows.count, 1) // Only the status bar window should be present
subject?.setupFloatingWindow(true)
let floatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) {
return true
}
return false
}
XCTAssertNotNil(floatingWindow)
XCTAssertEqual(floatingWindow?.windowController?.windowFrameAutosaveName, NSWindow.FrameAutosaveName("FloatingWindowAutoSave"))
subject?.setupFloatingWindow(false)
let closedFloatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) {
return true
}
return false
}
XCTAssertNotNil(closedFloatingWindow)
}
}

51
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -105,31 +105,31 @@ class ClockerUnitTests: XCTestCase {
XCTAssert(newDefaults.isEmpty == false) XCTAssert(newDefaults.isEmpty == false)
XCTAssert(newDefaults.count == oldCount + 1) XCTAssert(newDefaults.count == oldCount + 1)
} }
func testDecoding() { func testDecoding() {
let timezone1 = TimezoneData.customObject(from: nil) let timezone1 = TimezoneData.customObject(from: nil)
XCTAssertNotNil(timezone1) XCTAssertNotNil(timezone1)
let data = Data() let data = Data()
let timezone2 = TimezoneData.customObject(from: data) let timezone2 = TimezoneData.customObject(from: data)
XCTAssertNil(timezone2) XCTAssertNil(timezone2)
} }
func testDescription() { func testDescription() {
let timezoneData = TimezoneData(with: california) let timezoneData = TimezoneData(with: california)
XCTAssertFalse(timezoneData.description.isEmpty) XCTAssertFalse(timezoneData.description.isEmpty)
XCTAssertFalse(timezoneData.debugDescription.isEmpty) XCTAssertFalse(timezoneData.debugDescription.isEmpty)
} }
func testHashing() { func testHashing() {
let timezoneData = TimezoneData(with: california) let timezoneData = TimezoneData(with: california)
XCTAssert(timezoneData.hash != -1) XCTAssert(timezoneData.hash != -1)
timezoneData.placeID = nil timezoneData.placeID = nil
timezoneData.timezoneID = nil timezoneData.timezoneID = nil
XCTAssert(timezoneData.hash == -1) XCTAssert(timezoneData.hash == -1)
} }
func testBadInputDictionaryForInitialization() { func testBadInputDictionaryForInitialization() {
let badInput: [String: Any] = ["customLabel": "", let badInput: [String: Any] = ["customLabel": "",
"latitude": "41.2565369", "latitude": "41.2565369",
@ -234,7 +234,7 @@ class ClockerUnitTests: XCTestCase {
dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss") XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss")
// Wrong input // Wrong input
dataObject.setShouldOverrideGlobalTimeFormat(0) // 12-hour with preceding zero and seconds dataObject.setShouldOverrideGlobalTimeFormat(0) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(88) == "h:mm a") XCTAssertTrue(dataObject.timezoneFormat(88) == "h:mm a")
@ -274,47 +274,47 @@ class ClockerUnitTests: XCTestCase {
dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss") XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss")
dataObject.setShouldOverrideGlobalTimeFormat(12) // 12-hour with preceding zero and seconds dataObject.setShouldOverrideGlobalTimeFormat(12) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "epoch") XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "epoch")
} }
func testSecondsDisplayForOverridenTimezone() { func testSecondsDisplayForOverridenTimezone() {
let dataObject = TimezoneData(with: california) let dataObject = TimezoneData(with: california)
UserDefaults.standard.set(NSNumber(value: 1), forKey: CLSelectedTimeZoneFormatKey) // Set to 24-Hour Format UserDefaults.standard.set(NSNumber(value: 1), forKey: CLSelectedTimeZoneFormatKey) // Set to 24-Hour Format
// Test default behaviour // Test default behaviour
let timezoneWithSecondsKeys = [4,5,8,11] let timezoneWithSecondsKeys = [4, 5, 8, 11]
for timezoneKey in timezoneWithSecondsKeys { for timezoneKey in timezoneWithSecondsKeys {
dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey) dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey)
XCTAssertTrue(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) XCTAssertTrue(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
} }
let timezoneWithoutSecondsKeys = [1,2,7,10] let timezoneWithoutSecondsKeys = [1, 2, 7, 10]
for timezoneKey in timezoneWithoutSecondsKeys { for timezoneKey in timezoneWithoutSecondsKeys {
dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey) dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey)
XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
} }
// Test wrong override timezone key // Test wrong override timezone key
let wrongTimezoneKey = 88 let wrongTimezoneKey = 88
dataObject.setShouldOverrideGlobalTimeFormat(wrongTimezoneKey) dataObject.setShouldOverrideGlobalTimeFormat(wrongTimezoneKey)
XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat())) XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
// Test wrong global preference key // Test wrong global preference key
dataObject.setShouldOverrideGlobalTimeFormat(0) dataObject.setShouldOverrideGlobalTimeFormat(0)
XCTAssertFalse(dataObject.shouldShowSeconds(88)) XCTAssertFalse(dataObject.shouldShowSeconds(88))
} }
func testTimezoneRetrieval() { func testTimezoneRetrieval() {
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
let autoupdatingTimezone = TimeZone.autoupdatingCurrent.identifier let autoupdatingTimezone = TimeZone.autoupdatingCurrent.identifier
XCTAssertEqual(dataObject.timezone(), "Asia/Calcutta") XCTAssertEqual(dataObject.timezone(), "Asia/Calcutta")
// Unlikely // Unlikely
dataObject.timezoneID = nil dataObject.timezoneID = nil
XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone) XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone)
dataObject.isSystemTimezone = true dataObject.isSystemTimezone = true
XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone) XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone)
} }
@ -328,15 +328,15 @@ class ClockerUnitTests: XCTestCase {
dataObject.formattedAddress = nil dataObject.formattedAddress = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
dataObject.setLabel("Jogeshwari") dataObject.setLabel("Jogeshwari")
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Jogeshwari", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Jogeshwari", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
// Unlikely scenario // Unlikely scenario
dataObject.setLabel("") dataObject.setLabel("")
dataObject.timezoneID = "GMT" dataObject.timezoneID = "GMT"
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "GMT", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())") XCTAssertTrue(dataObject.formattedTimezoneLabel() == "GMT", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
// Another unlikely scenario // Another unlikely scenario
dataObject.setLabel("") dataObject.setLabel("")
dataObject.timezoneID = nil dataObject.timezoneID = nil
@ -392,4 +392,15 @@ class ClockerUnitTests: XCTestCase {
XCTAssertNotNil(convertedDate) XCTAssertNotNil(convertedDate)
} }
} }
func testStringFiltering() {
let stringWithComma = "Mumbai, Maharashtra"
let stringWithoutComma = "Mumbai"
let emptyString = ""
XCTAssertEqual(stringWithComma.filteredName(), "Mumbai")
XCTAssertEqual(stringWithoutComma.filteredName(), "Mumbai")
XCTAssertEqual(emptyString.filteredName(), "")
}
} }

36
Clocker/ClockerUnitTests/DateFormatterManagerTests.swift

@ -0,0 +1,36 @@
// Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker
class DateFormatterManagerTests: XCTestCase {
func testRegularDateFormatter() throws {
let subject = DateFormatterManager.dateFormatter(with: .medium, for: "UTC")
XCTAssertEqual(subject.dateStyle, .medium)
XCTAssertEqual(subject.timeStyle, .medium)
XCTAssertEqual(subject.locale.identifier, "en_US")
XCTAssertEqual(subject.timeZone.identifier, "GMT")
}
func testDateFormatterWithFormat() throws {
let subject = DateFormatterManager.dateFormatterWithFormat(with: .none, format: "hh:mm a", timezoneIdentifier: "Asia/Calcutta")
XCTAssertEqual(subject.dateStyle, .none)
XCTAssertEqual(subject.timeStyle, .none)
XCTAssertEqual(subject.locale.identifier, "en_US")
XCTAssertEqual(subject.timeZone.identifier, "Asia/Calcutta")
XCTAssertEqual(subject.locale.identifier, "en_US")
XCTAssertEqual(subject.dateFormat, "hh:mm a")
}
func testLocalizedDateFormatter() throws {
let subject = DateFormatterManager.localizedFormatter(with: "hh:mm:ss", for: "America/Los_Angeles")
XCTAssertEqual(subject.dateStyle, .none)
XCTAssertEqual(subject.timeStyle, .none)
XCTAssertEqual(subject.locale.identifier, Locale.autoupdatingCurrent.identifier)
XCTAssertEqual(subject.timeZone.identifier, "America/Los_Angeles")
XCTAssertEqual(subject.dateFormat, "hh:mm:ss")
}
}

4
Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift

@ -30,7 +30,7 @@ class StandardMenubarHandlerTests: XCTestCase {
// Set standard menubar in Prefs // Set standard menubar in Prefs
UserDefaults.standard.set(1, forKey: CLMenubarCompactMode) UserDefaults.standard.set(1, forKey: CLMenubarCompactMode)
let menubarHandler = MenubarHandler() let menubarHandler = MenubarTitleProvider()
let menubarString = menubarHandler.titleForMenubar() ?? "" let menubarString = menubarHandler.titleForMenubar() ?? ""
// Test menubar string is present // Test menubar string is present
@ -58,7 +58,7 @@ class StandardMenubarHandlerTests: XCTestCase {
func testUnfavouritedTimezone_returnNilMenubarString() { func testUnfavouritedTimezone_returnNilMenubarString() {
// Wipe all timezones from UserDefaults // Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil) DataStore.shared().setTimezones(nil)
let menubarHandler = MenubarHandler() let menubarHandler = MenubarTitleProvider()
let emptyMenubarString = menubarHandler.titleForMenubar() let emptyMenubarString = menubarHandler.titleForMenubar()
// Returns early because DataStore.menubarTimezones is nil // Returns early because DataStore.menubarTimezones is nil
XCTAssertNil(emptyMenubarString) XCTAssertNil(emptyMenubarString)

44
Clocker/ClockerUnitTests/ThemerTests.swift

@ -5,16 +5,15 @@ import XCTest
@testable import Clocker @testable import Clocker
class ThemerTests: XCTestCase { class ThemerTests: XCTestCase {
@available(macOS 10.14, *) @available(macOS 10.14, *)
func testSettingTheme() { func testSettingTheme() {
// Set to some random number should set to 0 // Set to some random number should set to 0
let subject = Themer(index: 124) let subject = Themer(index: 124)
XCTAssertEqual(NSAppearance(named: .aqua), NSAppearance(named: .aqua)) XCTAssertEqual(NSAppearance(named: .aqua), NSAppearance(named: .aqua))
// Set the same theme; this should return early // Set the same theme; this should return early
subject.set(theme: 0) subject.set(theme: 0)
// Set the theme to dark theme // Set the theme to dark theme
subject.set(theme: 1) subject.set(theme: 1)
let expectedApperance = NSAppearance(named: .darkAqua) let expectedApperance = NSAppearance(named: .darkAqua)
@ -28,8 +27,7 @@ class ThemerTests: XCTestCase {
let expectedBackgroundColor = NSColor.white let expectedBackgroundColor = NSColor.white
let expectedTextColor = NSColor.black let expectedTextColor = NSColor.black
let expectedTextBackgroundColor = NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) let expectedTextBackgroundColor = NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0)
let expectedShutdownImageName = "ellipsis.circle" let expectedShutdownImageName = "ellipsis.circle"
let expectedPreferenceImageName = "plus" let expectedPreferenceImageName = "plus"
let expectedPinImageName = "macwindow.on.rectangle" let expectedPinImageName = "macwindow.on.rectangle"
@ -54,14 +52,13 @@ class ThemerTests: XCTestCase {
let expectedBackwardsImage = "gobackward.15" let expectedBackwardsImage = "gobackward.15"
let expectedForwardsImage = "goforward.15" let expectedForwardsImage = "goforward.15"
let expectedResetSliderImage = "xmark.circle.fill" let expectedResetSliderImage = "xmark.circle.fill"
XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor) XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor) XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor)
XCTAssertEqual(subject.mainBackgroundColor(), expectedBackgroundColor) XCTAssertEqual(subject.mainBackgroundColor(), expectedBackgroundColor)
XCTAssertEqual(subject.mainTextColor(), expectedTextColor) XCTAssertEqual(subject.mainTextColor(), expectedTextColor)
XCTAssertEqual(subject.textBackgroundColor(), expectedTextBackgroundColor) XCTAssertEqual(subject.textBackgroundColor(), expectedTextBackgroundColor)
XCTAssertEqual(subject.shutdownImage().accessibilityDescription, expectedShutdownImageName) XCTAssertEqual(subject.shutdownImage().accessibilityDescription, expectedShutdownImageName)
XCTAssertEqual(subject.preferenceImage().accessibilityDescription, expectedPreferenceImageName) XCTAssertEqual(subject.preferenceImage().accessibilityDescription, expectedPreferenceImageName)
XCTAssertEqual(subject.pinImage().accessibilityDescription, expectedPinImageName) XCTAssertEqual(subject.pinImage().accessibilityDescription, expectedPinImageName)
@ -74,7 +71,7 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.sharingImage().accessibilityDescription, expectedSharingImage) XCTAssertEqual(subject.sharingImage().accessibilityDescription, expectedSharingImage)
XCTAssertEqual(subject.currentLocationImage().accessibilityDescription, expectedCurrentLocationImage) XCTAssertEqual(subject.currentLocationImage().accessibilityDescription, expectedCurrentLocationImage)
XCTAssertEqual(subject.popoverAppearance(), expectedPopoverApperarance) XCTAssertEqual(subject.popoverAppearance(), expectedPopoverApperarance)
XCTAssertEqual(subject.addImage().accessibilityDescription, expectedAddImage) XCTAssertEqual(subject.addImage().accessibilityDescription, expectedAddImage)
XCTAssertEqual(subject.addImageHighlighted().name(), expectedAddImageHighlighted) XCTAssertEqual(subject.addImageHighlighted().name(), expectedAddImageHighlighted)
XCTAssertEqual(subject.privacyTabImage().accessibilityDescription, expectedPrivacyTabImage) XCTAssertEqual(subject.privacyTabImage().accessibilityDescription, expectedPrivacyTabImage)
@ -88,7 +85,7 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage) XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage)
XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage)
} }
func testDarkTheme() throws { func testDarkTheme() throws {
let subject = Themer(index: 1) // 1 is for dark theme let subject = Themer(index: 1) // 1 is for dark theme
let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
@ -96,8 +93,7 @@ class ThemerTests: XCTestCase {
let expectedBackgroundColor = NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0) let expectedBackgroundColor = NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0)
let expectedTextColor = NSColor.white let expectedTextColor = NSColor.white
let expectedTextBackgroundColor = NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) let expectedTextBackgroundColor = NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0)
let expectedShutdownImageName = "ellipsis.circle" let expectedShutdownImageName = "ellipsis.circle"
let expectedPreferenceImageName = "plus" let expectedPreferenceImageName = "plus"
let expectedPinImageName = "macwindow.on.rectangle" let expectedPinImageName = "macwindow.on.rectangle"
@ -122,7 +118,6 @@ class ThemerTests: XCTestCase {
let expectedBackwardsImage = "gobackward.15" let expectedBackwardsImage = "gobackward.15"
let expectedForwardsImage = "goforward.15" let expectedForwardsImage = "goforward.15"
let expectedResetSliderImage = "xmark.circle.fill" let expectedResetSliderImage = "xmark.circle.fill"
XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor) XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor) XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor)
@ -155,18 +150,17 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage)
XCTAssertEqual(subject.description, "Current Theme is \(Themer.Theme.dark)") XCTAssertEqual(subject.description, "Current Theme is \(Themer.Theme.dark)")
} }
func testSystemTheme() throws { func testSystemTheme() throws {
let currentSystemTheme = let currentSystemTheme =
UserDefaults.standard.string(forKey: "AppleUserInterfaceStyle")?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light UserDefaults.standard.string(forKey: "AppleUserInterfaceStyle")?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light
let subject = Themer(index: 2) // 2 is for system theme let subject = Themer(index: 2) // 2 is for system theme
let expectedSliderKnobColor = currentSystemTheme == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderKnobColor = currentSystemTheme == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
let expectedSliderRightColor = currentSystemTheme == .dark ? NSColor.white : NSColor.gray let expectedSliderRightColor = currentSystemTheme == .dark ? NSColor.white : NSColor.gray
let expectedBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0) : NSColor.white let expectedBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0) : NSColor.white
let expectedTextColor = NSColor.textColor let expectedTextColor = NSColor.textColor
let expectedTextBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) : NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) let expectedTextBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) : NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0)
let expectedShutdownImageName = "ellipsis.circle" let expectedShutdownImageName = "ellipsis.circle"
let expectedPreferenceImageName = "plus" let expectedPreferenceImageName = "plus"
let expectedPinImageName = "macwindow.on.rectangle" let expectedPinImageName = "macwindow.on.rectangle"
@ -191,7 +185,6 @@ class ThemerTests: XCTestCase {
let expectedBackwardsImage = "gobackward.15" let expectedBackwardsImage = "gobackward.15"
let expectedForwardsImage = "goforward.15" let expectedForwardsImage = "goforward.15"
let expectedResetSliderImage = "xmark.circle.fill" let expectedResetSliderImage = "xmark.circle.fill"
XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor) XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor) XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor)
@ -224,7 +217,7 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage)
XCTAssertEqual(subject.description, "System Theme is \(currentSystemTheme == .dark ? Themer.Theme.dark : Themer.Theme.light)") XCTAssertEqual(subject.description, "System Theme is \(currentSystemTheme == .dark ? Themer.Theme.dark : Themer.Theme.light)")
} }
func testSolarizedLightTheme() throws { func testSolarizedLightTheme() throws {
let subject = Themer(index: 3) // 3 is for solarized light theme let subject = Themer(index: 3) // 3 is for solarized light theme
let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
@ -232,8 +225,7 @@ class ThemerTests: XCTestCase {
let expectedBackgroundColor = NSColor(deviceRed: 253.0 / 255.0, green: 246.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0) let expectedBackgroundColor = NSColor(deviceRed: 253.0 / 255.0, green: 246.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0)
let expectedTextColor = NSColor.black let expectedTextColor = NSColor.black
let expectedTextBackgroundColor = NSColor(deviceRed: 238.0 / 255.0, green: 232.0 / 255.0, blue: 213.0 / 255.0, alpha: 1.0) let expectedTextBackgroundColor = NSColor(deviceRed: 238.0 / 255.0, green: 232.0 / 255.0, blue: 213.0 / 255.0, alpha: 1.0)
let expectedShutdownImageName = "ellipsis.circle" let expectedShutdownImageName = "ellipsis.circle"
let expectedPreferenceImageName = "plus" let expectedPreferenceImageName = "plus"
let expectedPinImageName = "macwindow.on.rectangle" let expectedPinImageName = "macwindow.on.rectangle"
@ -258,14 +250,13 @@ class ThemerTests: XCTestCase {
let expectedBackwardsImage = "gobackward.15" let expectedBackwardsImage = "gobackward.15"
let expectedForwardsImage = "goforward.15" let expectedForwardsImage = "goforward.15"
let expectedResetSliderImage = "xmark.circle.fill" let expectedResetSliderImage = "xmark.circle.fill"
XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor) XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor) XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor)
XCTAssertEqual(subject.mainBackgroundColor(), expectedBackgroundColor) XCTAssertEqual(subject.mainBackgroundColor(), expectedBackgroundColor)
XCTAssertEqual(subject.mainTextColor(), expectedTextColor) XCTAssertEqual(subject.mainTextColor(), expectedTextColor)
XCTAssertEqual(subject.textBackgroundColor(), expectedTextBackgroundColor) XCTAssertEqual(subject.textBackgroundColor(), expectedTextBackgroundColor)
XCTAssertEqual(subject.shutdownImage().accessibilityDescription, expectedShutdownImageName) XCTAssertEqual(subject.shutdownImage().accessibilityDescription, expectedShutdownImageName)
XCTAssertEqual(subject.preferenceImage().accessibilityDescription, expectedPreferenceImageName) XCTAssertEqual(subject.preferenceImage().accessibilityDescription, expectedPreferenceImageName)
XCTAssertEqual(subject.pinImage().accessibilityDescription, expectedPinImageName) XCTAssertEqual(subject.pinImage().accessibilityDescription, expectedPinImageName)
@ -278,7 +269,7 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.sharingImage().accessibilityDescription, expectedSharingImage) XCTAssertEqual(subject.sharingImage().accessibilityDescription, expectedSharingImage)
XCTAssertEqual(subject.currentLocationImage().accessibilityDescription, expectedCurrentLocationImage) XCTAssertEqual(subject.currentLocationImage().accessibilityDescription, expectedCurrentLocationImage)
XCTAssertEqual(subject.popoverAppearance(), expectedPopoverApperarance) XCTAssertEqual(subject.popoverAppearance(), expectedPopoverApperarance)
XCTAssertEqual(subject.addImage().accessibilityDescription, expectedAddImage) XCTAssertEqual(subject.addImage().accessibilityDescription, expectedAddImage)
XCTAssertEqual(subject.addImageHighlighted().name(), expectedAddImageHighlighted) XCTAssertEqual(subject.addImageHighlighted().name(), expectedAddImageHighlighted)
XCTAssertEqual(subject.privacyTabImage().accessibilityDescription, expectedPrivacyTabImage) XCTAssertEqual(subject.privacyTabImage().accessibilityDescription, expectedPrivacyTabImage)
@ -292,7 +283,7 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage) XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage)
XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage)
} }
func testSolarizedDarkTheme() throws { func testSolarizedDarkTheme() throws {
let subject = Themer(index: 4) // 4 is for solarized dark theme let subject = Themer(index: 4) // 4 is for solarized dark theme
let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
@ -300,8 +291,7 @@ class ThemerTests: XCTestCase {
let expectedBackgroundColor = NSColor(deviceRed: 7.0 / 255.0, green: 54.0 / 255.0, blue: 66.0 / 255.0, alpha: 1.0) let expectedBackgroundColor = NSColor(deviceRed: 7.0 / 255.0, green: 54.0 / 255.0, blue: 66.0 / 255.0, alpha: 1.0)
let expectedTextColor = NSColor.white let expectedTextColor = NSColor.white
let expectedTextBackgroundColor = NSColor(deviceRed: 88.0 / 255.0, green: 110.0 / 255.0, blue: 117.0 / 255.0, alpha: 1.0) let expectedTextBackgroundColor = NSColor(deviceRed: 88.0 / 255.0, green: 110.0 / 255.0, blue: 117.0 / 255.0, alpha: 1.0)
let expectedShutdownImageName = "ellipsis.circle" let expectedShutdownImageName = "ellipsis.circle"
let expectedPreferenceImageName = "plus" let expectedPreferenceImageName = "plus"
let expectedPinImageName = "macwindow.on.rectangle" let expectedPinImageName = "macwindow.on.rectangle"
@ -326,7 +316,6 @@ class ThemerTests: XCTestCase {
let expectedBackwardsImage = "gobackward.15" let expectedBackwardsImage = "gobackward.15"
let expectedForwardsImage = "goforward.15" let expectedForwardsImage = "goforward.15"
let expectedResetSliderImage = "xmark.circle.fill" let expectedResetSliderImage = "xmark.circle.fill"
XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor) XCTAssertEqual(subject.sliderKnobColor(), expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor) XCTAssertEqual(subject.sliderRightColor(), expectedSliderRightColor)
@ -358,5 +347,4 @@ class ThemerTests: XCTestCase {
XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage) XCTAssertEqual(subject.goForwardsImage()?.accessibilityDescription, expectedForwardsImage)
XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage) XCTAssertEqual(subject.resetModernSliderImage()?.accessibilityDescription, expectedResetSliderImage)
} }
} }

6
Clocker/CoreLoggerKit/Package.swift

@ -6,13 +6,13 @@ import PackageDescription
let package = Package( let package = Package(
name: "CoreLoggerKit", name: "CoreLoggerKit",
platforms: [ platforms: [
.macOS(.v10_12), .macOS(.v10_12)
], ],
products: [ products: [
.library( .library(
name: "CoreLoggerKit", name: "CoreLoggerKit",
targets: ["CoreLoggerKit"] targets: ["CoreLoggerKit"]
), )
], ],
dependencies: [], dependencies: [],
targets: [ targets: [
@ -23,6 +23,6 @@ let package = Package(
.testTarget( .testTarget(
name: "CoreLoggerKitTests", name: "CoreLoggerKitTests",
dependencies: ["CoreLoggerKit"] dependencies: ["CoreLoggerKit"]
), )
] ]
) )

2
Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/CoreLoggerKitTests.swift

@ -3,6 +3,6 @@ import XCTest
final class CoreLoggerKitTests: XCTestCase { final class CoreLoggerKitTests: XCTestCase {
static var allTests = [ static var allTests = [
("testExample", testExample), ("testExample", testExample)
] ]
} }

2
Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/XCTestManifests.swift

@ -3,7 +3,7 @@ import XCTest
#if !canImport(ObjectiveC) #if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] { public func allTests() -> [XCTestCaseEntry] {
return [ return [
testCase(CoreLoggerKitTests.allTests), testCase(CoreLoggerKitTests.allTests)
] ]
} }
#endif #endif

8
Clocker/CoreModelKit/Package.swift

@ -6,19 +6,19 @@ import PackageDescription
let package = Package( let package = Package(
name: "CoreModelKit", name: "CoreModelKit",
platforms: [ platforms: [
.macOS(.v10_12), .macOS(.v10_12)
], ],
products: [ products: [
// Products define the executables and libraries a package produces, and make them visible to other packages. // Products define the executables and libraries a package produces, and make them visible to other packages.
.library( .library(
name: "CoreModelKit", name: "CoreModelKit",
targets: ["CoreModelKit"] targets: ["CoreModelKit"]
), )
], ],
dependencies: [ dependencies: [
// Dependencies declare other packages that this package depends on. // Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"), // .package(url: /* package url */, from: "1.0.0"),
.package(path: "../CoreLoggerKit/"), .package(path: "../CoreLoggerKit/")
], ],
targets: [ targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets are the basic building blocks of a package. A target can define a module or a test suite.
@ -30,6 +30,6 @@ let package = Package(
.testTarget( .testTarget(
name: "CoreModelKitTests", name: "CoreModelKitTests",
dependencies: ["CoreModelKit", "CoreLoggerKit"] dependencies: ["CoreModelKit", "CoreLoggerKit"]
), )
] ]
) )

10
Clocker/CoreModelKit/Sources/CoreModelKit/SearchResults.swift

@ -2,7 +2,7 @@
import Cocoa import Cocoa
public struct ResultStatus { public enum ResultStatus {
public static let okay = "OK" public static let okay = "OK"
public static let zeroResults = "ZERO_RESULTS" public static let zeroResults = "ZERO_RESULTS"
public static let requestDenied = "REQUEST_DENIED" public static let requestDenied = "REQUEST_DENIED"
@ -12,7 +12,7 @@ public struct SearchResult: Codable {
public let results: [Result] public let results: [Result]
public let status: String public let status: String
public let errorMessage: String? public let errorMessage: String?
public struct Result: Codable { public struct Result: Codable {
public let addressComponents: [AddressComponent] public let addressComponents: [AddressComponent]
public let formattedAddress: String public let formattedAddress: String
@ -55,10 +55,10 @@ public struct SearchResult: Codable {
case types case types
} }
} }
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case results = "results" case results
case status = "status" case status
case errorMessage = "error_message" case errorMessage = "error_message"
} }
} }

8
Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift

@ -66,7 +66,7 @@ public class TimezoneData: NSObject, NSCoding {
// Suffix // Suffix
NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix, NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix,
NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds, NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds,
NSNumber(integerLiteral: 11): DateFormat.epochTime, NSNumber(integerLiteral: 11): DateFormat.epochTime
] ]
public var customLabel: String? public var customLabel: String?
@ -85,7 +85,7 @@ public class TimezoneData: NSObject, NSCoding {
public var isSystemTimezone = false public var isSystemTimezone = false
public var overrideFormat: TimezoneOverride = .globalFormat public var overrideFormat: TimezoneOverride = .globalFormat
public override init() { override public init() {
selectionType = .timezone selectionType = .timezone
isFavourite = 0 isFavourite = 0
note = ModelConstants.emptyString note = ModelConstants.emptyString
@ -268,7 +268,7 @@ public class TimezoneData: NSObject, NSCoding {
return formatInString.contains("ss") return formatInString.contains("ss")
} }
public override var hash: Int { override public var hash: Int {
guard let placeIdentifier = placeID, let timezone = timezoneID else { guard let placeIdentifier = placeID, let timezone = timezoneID else {
return -1 return -1
} }
@ -276,7 +276,7 @@ public class TimezoneData: NSObject, NSCoding {
return placeIdentifier.hashValue ^ timezone.hashValue return placeIdentifier.hashValue ^ timezone.hashValue
} }
public override func isEqual(_ object: Any?) -> Bool { override public func isEqual(_ object: Any?) -> Bool {
guard let compared = object as? TimezoneData else { guard let compared = object as? TimezoneData else {
return false return false
} }

2
Clocker/CoreModelKit/Tests/CoreModelKitTests/XCTestManifests.swift

@ -3,7 +3,7 @@ import XCTest
#if !canImport(ObjectiveC) #if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] { public func allTests() -> [XCTestCaseEntry] {
return [ return [
testCase(CoreModelKitTests.allTests), testCase(CoreModelKitTests.allTests)
] ]
} }
#endif #endif

6
Clocker/Dependencies/Date Additions/TimePeriodChain.swift

@ -138,15 +138,15 @@ open class TimePeriodChain: TimePeriodGroup {
_end = _end?.addingTimeInterval(duration) _end = _end?.addingTimeInterval(duration)
} }
public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { override public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform) return try periods.map(transform)
} }
public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { override public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded) return try periods.filter(isIncluded)
} }
internal override func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { override internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult) return try periods.reduce(initialResult, nextPartialResult)
} }

4
Clocker/Events and Reminders/CalendarHandler.swift

@ -163,7 +163,7 @@ extension EventCenter {
if filteredEvents.count == 1 { return filteredEvents.first } if filteredEvents.count == 1 { return filteredEvents.first }
// If there are multipl events coming up, prefer the ones the currentUser has accepted // If there are multiple events coming up, prefer the ones the currentUser has accepted
let acceptedEvents = filteredEvents.filter { let acceptedEvents = filteredEvents.filter {
$0.attendeStatus == .accepted $0.attendeStatus == .accepted
} }
@ -383,7 +383,7 @@ extension EventCenter {
// Borrowing logic from Ityscal // Borrowing logic from Ityscal
@discardableResult @discardableResult
private func findAppropriateURLs(_ description: String) -> URL? { private func findAppropriateURLs(_ description: String) -> URL? {
guard let results = EventCenter.dataDetector?.matches(in: description, options: .reportCompletion, range: NSMakeRange(0, description.count)) else { guard let results = EventCenter.dataDetector?.matches(in: description, options: .reportCompletion, range: NSRange(location: 0, length: description.count)) else {
return nil return nil
} }
for result in results { for result in results {

4
Clocker/Menu Bar/StatusContainerView.swift

@ -75,7 +75,7 @@ class StatusContainerView: NSView {
let timeBasedAttributes = [ let timeBasedAttributes = [
NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
] ]
func containerWidth(for timezones: [Data]) -> CGFloat { func containerWidth(for timezones: [Data]) -> CGFloat {
@ -134,7 +134,7 @@ class StatusContainerView: NSView {
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
] ]
let operation = TimezoneDataOperations(with: timezone) let operation = TimezoneDataOperations(with: timezone)

2
Clocker/Menu Bar/StatusItemHandler.swift

@ -185,7 +185,7 @@ class StatusItemHandler: NSObject {
if let strongSelf = self { if let strongSelf = self {
strongSelf.performTimerWork() strongSelf.performTimerWork()
} }
}) })
// Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval // Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval
menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20 menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20

8
Clocker/Menu Bar/StatusItemView.swift

@ -49,7 +49,7 @@ class StatusItemView: NSView {
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
} }
@ -61,7 +61,7 @@ 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
} }
@ -90,14 +90,14 @@ 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)
]) ])
} }

2
Clocker/Onboarding/FinalOnboardingViewController.swift

@ -53,7 +53,7 @@ class FinalOnboardingViewController: NSViewController {
@IBAction func localizationAction(_: Any) { @IBAction func localizationAction(_: Any) {
guard let localizationURL = URL(string: AboutUsConstants.TwitterFollowIntentLink), guard let localizationURL = URL(string: AboutUsConstants.TwitterFollowIntentLink),
let languageCode = Locale.preferredLanguages.first else { return } let languageCode = Locale.preferredLanguages.first else { return }
NSWorkspace.shared.open(localizationURL) NSWorkspace.shared.open(localizationURL)

12
Clocker/Onboarding/OnboardingSearchController.swift

@ -27,8 +27,8 @@ class OnboardingSearchController: NSViewController {
private var geocodingKey: String = { private var geocodingKey: String = {
guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"), guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"),
let dictionary = NSDictionary(contentsOfFile: path), let dictionary = NSDictionary(contentsOfFile: path),
let apiKey = dictionary["GeocodingKey"] as? String let apiKey = dictionary["GeocodingKey"] as? String
else { else {
assertionFailure("Unable to find the API key") assertionFailure("Unable to find the API key")
return "" return ""
@ -227,7 +227,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))
@ -375,7 +375,7 @@ class OnboardingSearchController: NSViewController {
self.findLocalSearchResultsForTimezones() self.findLocalSearchResultsForTimezones()
self.prepareUIForPresentingResults() self.prepareUIForPresentingResults()
} }
}) })
} }
private func presentErrorMessage(_ errorMessage: String) { private func presentErrorMessage(_ errorMessage: String) {
@ -412,7 +412,7 @@ class OnboardingSearchController: NSViewController {
CLTimezoneName: formattedAddress, CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress, CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString, CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId, CLPlaceIdentifier: result.placeId
] as [String: Any] ] as [String: Any]
return TimezoneData(with: totalPackage) return TimezoneData(with: totalPackage)
@ -483,7 +483,7 @@ class ResultSectionHeaderTableViewCell: NSTableCellView {
class OnboardingSelectionTableRowView: NSTableRowView { class OnboardingSelectionTableRowView: NSTableRowView {
override func drawSelection(in _: NSRect) { override func drawSelection(in _: NSRect) {
if selectionHighlightStyle != .none { if selectionHighlightStyle != .none {
let selectionRect = NSInsetRect(bounds, 1, 1) let selectionRect = bounds.insetBy(dx: 1, dy: 1)
NSColor(calibratedWhite: 0.4, alpha: 1).setStroke() NSColor(calibratedWhite: 0.4, alpha: 1).setStroke()
NSColor(calibratedWhite: 0.4, alpha: 1).setFill() NSColor(calibratedWhite: 0.4, alpha: 1).setFill()
let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6) let selectionPath = NSBezierPath(roundedRect: selectionRect, xRadius: 6, yRadius: 6)

4
Clocker/Overall App/ConfigExport.swift

@ -29,7 +29,7 @@ struct ConfigExport {
CLDefaultMenubarMode, CLDefaultMenubarMode,
CLInstallHomeIndicatorObject, CLInstallHomeIndicatorObject,
CLSwitchToCompactModeAlert, CLSwitchToCompactModeAlert,
CLDisplayDSTTransitionInfo, CLDisplayDSTTransitionInfo
]) ])
let dictionaryRep = UserDefaults.standard.dictionaryRepresentation() let dictionaryRep = UserDefaults.standard.dictionaryRepresentation()
var clockerPrefs: [String: Any] = [:] var clockerPrefs: [String: Any] = [:]
@ -53,7 +53,7 @@ struct ConfigExport {
"Latitude": customObject.latitude ?? 0.0, "Latitude": customObject.latitude ?? 0.0,
"Longitude": customObject.longitude ?? 0.0, "Longitude": customObject.longitude ?? 0.0,
"Place Identifier": customObject.placeID ?? "0.0", "Place Identifier": customObject.placeID ?? "0.0",
"Selection Type": "\(customObject.selectionType)", "Selection Type": "\(customObject.selectionType)"
] ]
return timezoneDictionary return timezoneDictionary
} }

12
Clocker/Overall App/DataStore.swift

@ -47,17 +47,13 @@ class DataStore: NSObject {
} }
func timezones() -> [Data] { func timezones() -> [Data] {
if let cloudPreferences = ubiquitousStore.object(forKey: CLDefaultPreferenceKey) as? [Data] {
return cloudPreferences
}
guard let preferences = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data] else { guard let preferences = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data] else {
return [] return []
} }
return preferences return preferences
} }
func setTimezones(_ timezones: [Data]?) { func setTimezones(_ timezones: [Data]?) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey) userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
// iCloud sync // iCloud sync
@ -164,9 +160,9 @@ class DataStore: NSObject {
return value == 0 return value == 0
} }
} }
//MARK: Private // MARK: Private
private func shouldDisplayHelper(_ key: String) -> Bool { private func shouldDisplayHelper(_ key: String) -> Bool {
guard let value = retrieve(key: key) as? NSNumber else { guard let value = retrieve(key: key) as? NSNumber else {
return false return false

25
Clocker/Overall App/DateFormatterManager.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
class DateFormatterManager: NSObject { class DateFormatterManager: NSObject {
public static let sharedInstance = DateFormatterManager()
private static var dateFormatter = DateFormatter() private static var dateFormatter = DateFormatter()
private static var calendarDateFormatter = DateFormatter() private static var calendarDateFormatter = DateFormatter()
@ -22,21 +21,6 @@ class DateFormatterManager: NSObject {
return dateFormatter return dateFormatter
} }
class func dateFormatterWithCalendar(with style: DateFormatter.Style) -> DateFormatter {
calendarDateFormatter.dateStyle = style
calendarDateFormatter.timeStyle = style
calendarDateFormatter.locale = USLocale
calendarDateFormatter.calendar = gregorianCalendar
return calendarDateFormatter
}
class func simpleFormatter(with style: DateFormatter.Style) -> DateFormatter {
simpleFormatter.dateStyle = style
simpleFormatter.timeStyle = style
simpleFormatter.locale = USLocale
return simpleFormatter
}
class func dateFormatterWithFormat(with style: DateFormatter.Style, format: String, timezoneIdentifier: String, locale: Locale = Locale(identifier: "en_US")) -> DateFormatter { class func dateFormatterWithFormat(with style: DateFormatter.Style, format: String, timezoneIdentifier: String, locale: Locale = Locale(identifier: "en_US")) -> DateFormatter {
specializedFormatter.dateStyle = style specializedFormatter.dateStyle = style
specializedFormatter.timeStyle = style specializedFormatter.timeStyle = style
@ -55,15 +39,6 @@ class DateFormatterManager: NSObject {
return dateFormatter return dateFormatter
} }
class func localizedCalendaricalDateFormatter(with format: String) -> DateFormatter {
calendarDateFormatter.dateStyle = .none
calendarDateFormatter.timeStyle = .none
calendarDateFormatter.locale = Locale.autoupdatingCurrent
calendarDateFormatter.dateFormat = format
calendarDateFormatter.calendar = gregorianCalendar
return calendarDateFormatter
}
class func localizedSimpleFormatter(_ format: String) -> DateFormatter { class func localizedSimpleFormatter(_ format: String) -> DateFormatter {
localizedSimpleFormatter.dateStyle = .none localizedSimpleFormatter.dateStyle = .none
localizedSimpleFormatter.timeStyle = .none localizedSimpleFormatter.timeStyle = .none

2
Clocker/Overall App/NetworkManager.swift

@ -41,7 +41,7 @@ extension NetworkManager {
let session = URLSession(configuration: configuration) let session = URLSession(configuration: configuration)
guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: encodedPath) let url = URL(string: encodedPath)
else { else {
completionHandler(nil, unableToGenerateURL) completionHandler(nil, unableToGenerateURL)
return nil return nil

60
Clocker/Overall App/Themer.swift

@ -15,6 +15,7 @@ class Themer: NSObject {
case solarizedLight case solarizedLight
case solarizedDark case solarizedDark
} }
private static var sharedInstance = Themer(index: UserDefaults.standard.integer(forKey: CLThemeKey)) private static var sharedInstance = Themer(index: UserDefaults.standard.integer(forKey: CLThemeKey))
private var effectiveApperanceObserver: NSKeyValueObservation? private var effectiveApperanceObserver: NSKeyValueObservation?
private var themeIndex: Theme { private var themeIndex: Theme {
@ -47,13 +48,10 @@ class Themer: NSObject {
selector: #selector(respondToInterfaceStyle), selector: #selector(respondToInterfaceStyle),
name: .interfaceStyleDidChange, name: .interfaceStyleDidChange,
object: nil) object: nil)
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
effectiveApperanceObserver = NSApp.observe(\.effectiveAppearance) { [weak self] (app, _) in effectiveApperanceObserver = NSApp.observe(\.effectiveAppearance) { _, _ in
if let sSelf = self { NotificationCenter.default.post(name: .themeDidChangeNotification, object: nil)
sSelf.setAppAppearance()
NotificationCenter.default.post(name: .themeDidChangeNotification, object: nil)
}
} }
} }
} }
@ -92,12 +90,13 @@ extension Themer {
self.setAppAppearance() self.setAppAppearance()
} }
} }
//MARK: Color // MARK: Color
func sliderKnobColor() -> NSColor { func sliderKnobColor() -> NSColor {
switch themeIndex { switch themeIndex {
case .light: case .light:
return NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) return NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9)
case .system: case .system:
return retrieveCurrentSystem() == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) return retrieveCurrentSystem() == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
default: default:
@ -108,7 +107,7 @@ extension Themer {
func sliderRightColor() -> NSColor { func sliderRightColor() -> NSColor {
switch themeIndex { switch themeIndex {
case .dark: case .dark:
return NSColor.white return NSColor.white
default: default:
return NSColor.gray return NSColor.gray
} }
@ -132,7 +131,7 @@ extension Themer {
return themeIndex == .light ? NSColor.white : NSColor(deviceRed: 55.0 / 255.0, green: 71.0 / 255.0, blue: 79.0 / 255.0, alpha: 1.0) return themeIndex == .light ? NSColor.white : NSColor(deviceRed: 55.0 / 255.0, green: 71.0 / 255.0, blue: 79.0 / 255.0, alpha: 1.0)
} }
func textBackgroundColor() -> NSColor { func textBackgroundColor() -> NSColor {
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
@ -172,8 +171,9 @@ extension Themer {
return themeIndex == .light ? NSColor.black : NSColor.white return themeIndex == .light ? NSColor.black : NSColor.white
} }
//MARK: Images // MARK: Images
func shutdownImage() -> NSImage { func shutdownImage() -> NSImage {
if let symbolImageForShutdown = symbolImage(for: "ellipsis.circle") { if let symbolImageForShutdown = symbolImage(for: "ellipsis.circle") {
return symbolImageForShutdown return symbolImageForShutdown
@ -190,7 +190,7 @@ extension Themer {
if let symbolImageForPreference = symbolImage(for: "plus") { if let symbolImageForPreference = symbolImage(for: "plus") {
return symbolImageForPreference return symbolImageForPreference
} }
return fallbackImageProvider(NSImage(named: NSImage.Name("Settings"))!, return fallbackImageProvider(NSImage(named: NSImage.Name("Settings"))!,
NSImage(named: NSImage.Name("Settings-White"))!, NSImage(named: NSImage.Name("Settings-White"))!,
NSImage(named: NSImage.actionTemplateName)!, NSImage(named: NSImage.actionTemplateName)!,
@ -202,7 +202,7 @@ extension Themer {
if let pinImage = symbolImage(for: "macwindow.on.rectangle") { if let pinImage = symbolImage(for: "macwindow.on.rectangle") {
return pinImage return pinImage
} }
return fallbackImageProvider(NSImage(named: NSImage.Name("Float"))!, return fallbackImageProvider(NSImage(named: NSImage.Name("Float"))!,
NSImage(named: NSImage.Name("Float-White"))!, NSImage(named: NSImage.Name("Float-White"))!,
NSImage(named: NSImage.Name("Pin"))!, NSImage(named: NSImage.Name("Pin"))!,
@ -225,7 +225,7 @@ extension Themer {
if let symbolImage = symbolImage(for: "sunset.fill") { if let symbolImage = symbolImage(for: "sunset.fill") {
return symbolImage return symbolImage
} }
return fallbackImageProvider(NSImage(named: NSImage.Name("Sunset"))!, return fallbackImageProvider(NSImage(named: NSImage.Name("Sunset"))!,
NSImage(named: NSImage.Name("WhiteSunset"))!, NSImage(named: NSImage.Name("WhiteSunset"))!,
NSImage(named: NSImage.Name("Sunset Dynamic"))!, NSImage(named: NSImage.Name("Sunset Dynamic"))!,
@ -237,7 +237,7 @@ extension Themer {
if let symbolImage = symbolImage(for: "xmark") { if let symbolImage = symbolImage(for: "xmark") {
return symbolImage return symbolImage
} }
return fallbackImageProvider(NSImage(named: NSImage.Name("Remove"))!, return fallbackImageProvider(NSImage(named: NSImage.Name("Remove"))!,
NSImage(named: NSImage.Name("WhiteRemove"))!, NSImage(named: NSImage.Name("WhiteRemove"))!,
NSImage(named: NSImage.Name("Remove Dynamic"))!, NSImage(named: NSImage.Name("Remove Dynamic"))!,
@ -317,7 +317,7 @@ extension Themer {
if let symbolImageForPreference = symbolImage(for: "plus") { if let symbolImageForPreference = symbolImage(for: "plus") {
return symbolImageForPreference return symbolImageForPreference
} }
return fallbackImageProvider(NSImage(named: NSImage.Name("Add Icon"))!, return fallbackImageProvider(NSImage(named: NSImage.Name("Add Icon"))!,
NSImage(named: NSImage.Name("Add White"))!, NSImage(named: NSImage.Name("Add White"))!,
NSImage(named: .addDynamicIcon)!, NSImage(named: .addDynamicIcon)!,
@ -402,21 +402,21 @@ extension Themer {
return removeImage() return removeImage()
} }
//MARK: Debug Description // MARK: Debug Description
override var debugDescription: String { override var debugDescription: String {
if themeIndex == .system { if themeIndex == .system {
return "System Theme is \(retrieveCurrentSystem())" return "System Theme is \(retrieveCurrentSystem())"
} }
return "Current Theme is \(themeIndex)" return "Current Theme is \(themeIndex)"
} }
override var description: String { override var description: String {
return debugDescription return debugDescription
} }
//MARK: Private // MARK: Private
private func symbolImage(for name: String) -> NSImage? { private func symbolImage(for name: String) -> NSImage? {
assert(name.isEmpty == false) assert(name.isEmpty == false)
@ -428,7 +428,7 @@ extension Themer {
return nil return nil
} }
private func retrieveCurrentSystem() -> Theme { private func retrieveCurrentSystem() -> Theme {
if #available(OSX 10.14, *) { if #available(OSX 10.14, *) {
if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") as? String { if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") as? String {
@ -439,7 +439,7 @@ extension Themer {
} }
return .light return .light
} }
private func setAppAppearance() { private func setAppAppearance() {
if #available(OSX 10.14, *) { if #available(OSX 10.14, *) {
var appAppearance = NSAppearance(named: .aqua) var appAppearance = NSAppearance(named: .aqua)
@ -449,10 +449,12 @@ extension Themer {
} else if themeIndex == .system { } else if themeIndex == .system {
appAppearance = retrieveCurrentSystem() == .dark ? NSAppearance(named: .darkAqua) : NSAppearance(named: .aqua) appAppearance = retrieveCurrentSystem() == .dark ? NSAppearance(named: .darkAqua) : NSAppearance(named: .aqua)
} }
NSApp.appearance = appAppearance if NSApp.appearance != appAppearance {
NSApp.appearance = appAppearance
}
} }
} }
private func fallbackImageProvider(_ lightImage: NSImage, private func fallbackImageProvider(_ lightImage: NSImage,
_ darkImage: NSImage, _ darkImage: NSImage,
_ systemImage: NSImage, _ systemImage: NSImage,
@ -474,7 +476,5 @@ extension Themer {
} }
return themeIndex == .light ? lightImage : darkImage return themeIndex == .light ? lightImage : darkImage
} }
} }

2
Clocker/Overall App/Timer.swift

@ -118,7 +118,7 @@ open class Repeater: Equatable {
} }
/// Handler typealias /// Handler typealias
public typealias Observer = ((Repeater) -> Void) public typealias Observer = (Repeater) -> Void
/// Token assigned to the observer /// Token assigned to the observer
public typealias ObserverToken = UInt64 public typealias ObserverToken = UInt64

10
Clocker/Overall App/VersionUpdateHandler.swift

@ -172,7 +172,7 @@ class VersionUpdateHandler: NSObject {
lastVersionCopy = "0" lastVersionCopy = "0"
} }
var newVersionFound = false var newVersionFound = false
var details: String = "" var details = ""
let versions = dict.keys.sorted() let versions = dict.keys.sorted()
@ -242,20 +242,20 @@ class VersionUpdateHandler: NSObject {
y: 0.0, y: 0.0,
width: contentSize.width, width: contentSize.width,
height: contentSize.height)) height: contentSize.height))
textView.minSize = NSMakeSize(0.0, contentSize.height) textView.minSize = NSSize(width: 0.0, height: contentSize.height)
textView.maxSize = NSMakeSize(floatMax, floatMax) textView.maxSize = NSSize(width: floatMax, height: floatMax)
textView.isVerticallyResizable = true textView.isVerticallyResizable = true
textView.isHorizontallyResizable = false textView.isHorizontallyResizable = false
textView.isEditable = false textView.isEditable = false
textView.autoresizingMask = .width textView.autoresizingMask = .width
textView.textContainer?.containerSize = NSMakeSize(contentSize.width, floatMax) textView.textContainer?.containerSize = NSSize(width: contentSize.width, height: floatMax)
textView.textContainer?.widthTracksTextView = true textView.textContainer?.widthTracksTextView = true
textView.string = details textView.string = details
scrollView.documentView = textView scrollView.documentView = textView
textView.sizeToFit() textView.sizeToFit()
let height = min(200.0, scrollView.documentView?.frame.size.height ?? 200.0) + 3.0 let height = min(200.0, scrollView.documentView?.frame.size.height ?? 200.0) + 3.0
scrollView.frame = NSMakeRect(0.0, 0.0, scrollView.frame.size.width, height) scrollView.frame = NSRect(x: 0.0, y: 0.0, width: scrollView.frame.size.width, height: height)
alert.accessoryView = scrollView alert.accessoryView = scrollView
if ignoreButton.isEmpty == false { if ignoreButton.isEmpty == false {

30
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -77,32 +77,6 @@ extension TimezoneDataOperations {
return "Heads up! DST transition will occur in \(numberOfDays) \(suffix)." return "Heads up! DST transition will occur in \(numberOfDays) \(suffix)."
} }
private func checkForUpcomingEvents() -> (String, String)? {
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
let filteredDates = EventCenter.sharedCenter().eventsForDate
let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar
guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else {
return nil
}
for event in events {
if event.event.startDate.timeIntervalSinceNow > 0, !event.isAllDay {
let timeForEventToStart = event.event.startDate.timeIntervalSinceNow / 60
if timeForEventToStart > 30 {
Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins")
continue
}
return EventCenter.sharedCenter().separateFormat(event: event.event)
}
}
}
return nil
}
func compactMenuTitle() -> String { func compactMenuTitle() -> String {
var subtitle = CLEmptyString var subtitle = CLEmptyString
@ -309,7 +283,7 @@ 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
@ -368,7 +342,7 @@ extension TimezoneDataOperations {
to: Date()) to: Date())
guard let lat = dataObject.latitude, guard let lat = dataObject.latitude,
let long = dataObject.longitude let long = dataObject.longitude
else { else {
assertionFailure("Data was unexpectedly nil.") assertionFailure("Data was unexpectedly nil.")
return return

6
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -73,7 +73,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()
@ -224,7 +224,7 @@ class NotesPopover: NSViewController {
} }
completionHandler(completedDate) completionHandler(completedDate)
}) })
} catch { } catch {
assertionFailure("Failed to successfully initialize DataDetector") assertionFailure("Failed to successfully initialize DataDetector")
completionHandler(nil) completionHandler(nil)
@ -240,7 +240,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,

2
Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift

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

24
Clocker/Panel/PanelController.swift

@ -154,7 +154,7 @@ class PanelController: ParentPanelController {
} }
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
var testPoint = statusItemFrame.origin var testPoint = statusItemFrame.origin
@ -186,16 +186,16 @@ class PanelController: ParentPanelController {
let preferences = DataStore.shared().timezones() let preferences = DataStore.shared().timezones()
guard let theme = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber, guard let theme = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber,
let displayFutureSliderKey = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber, let displayFutureSliderKey = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber,
let showAppInForeground = DataStore.shared().retrieve(key: CLShowAppInForeground) as? NSNumber, let showAppInForeground = DataStore.shared().retrieve(key: CLShowAppInForeground) as? NSNumber,
let relativeDateKey = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber, let relativeDateKey = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber,
let fontSize = DataStore.shared().retrieve(key: CLUserFontSizePreference) as? NSNumber, let fontSize = DataStore.shared().retrieve(key: CLUserFontSizePreference) as? NSNumber,
let sunriseTime = DataStore.shared().retrieve(key: CLSunriseSunsetTime) as? NSNumber, let sunriseTime = DataStore.shared().retrieve(key: CLSunriseSunsetTime) as? NSNumber,
let showDayInMenu = DataStore.shared().retrieve(key: CLShowDayInMenu) as? NSNumber, let showDayInMenu = DataStore.shared().retrieve(key: CLShowDayInMenu) as? NSNumber,
let showDateInMenu = DataStore.shared().retrieve(key: CLShowDateInMenu) as? NSNumber, let showDateInMenu = DataStore.shared().retrieve(key: CLShowDateInMenu) as? NSNumber,
let showPlaceInMenu = DataStore.shared().retrieve(key: CLShowPlaceInMenu) as? NSNumber, let showPlaceInMenu = DataStore.shared().retrieve(key: CLShowPlaceInMenu) as? NSNumber,
let showUpcomingEventView = DataStore.shared().retrieve(key: CLShowUpcomingEventView) as? String, let showUpcomingEventView = DataStore.shared().retrieve(key: CLShowUpcomingEventView) as? String,
let country = Locale.autoupdatingCurrent.regionCode let country = Locale.autoupdatingCurrent.regionCode
else { else {
return return
} }
@ -221,7 +221,7 @@ 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")

2
Clocker/Panel/ParentPanelController+ModernSlider.swift

@ -127,7 +127,7 @@ extension ParentPanelController {
} }
public func setDefaultDateLabel(_ index: Int) -> Int { public func setDefaultDateLabel(_ index: Int) -> Int {
let futureSliderDayPreference = DataStore.shared().retrieve(key: CLFutureSliderRange) as? NSNumber ?? 5 let futureSliderDayPreference = DataStore.shared().retrieve(key: CLFutureSliderRange) as? NSNumber ?? 5
let futureSliderDayRange = (futureSliderDayPreference.intValue + 1) let futureSliderDayRange = (futureSliderDayPreference.intValue + 1)
let totalCount = (PanelConstants.modernSliderPointsInADay * futureSliderDayRange * 2) + 1 let totalCount = (PanelConstants.modernSliderPointsInADay * futureSliderDayRange * 2) + 1
let centerPoint = Int(ceil(Double(totalCount / 2))) let centerPoint = Int(ceil(Double(totalCount / 2)))

53
Clocker/Panel/ParentPanelController.swift

@ -172,6 +172,15 @@ class ParentPanelController: NSWindowController {
setupObservers() setupObservers()
updateReviewViewFontColor() updateReviewViewFontColor()
// Set the background color of the bottom buttons view to something different to indicate we're not in a release candidate
#if DEBUG
stackView.arrangedSubviews.last?.layer?.backgroundColor = NSColor(deviceRed: 255.0/255.0,
green: 150.0/255.0,
blue: 122.0/255.0,
alpha: 0.5).cgColor
stackView.arrangedSubviews.last?.toolTip = "Clocker is running in Debug Mode"
#endif
// Setup layers // Setup layers
futureSliderView.wantsLayer = true futureSliderView.wantsLayer = true
@ -250,7 +259,7 @@ class ParentPanelController: NSWindowController {
coordinates: nil) coordinates: nil)
} }
} }
private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) { private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) {
let timezones = DataStore.shared().timezones() let timezones = DataStore.shared().timezones()
@ -340,7 +349,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)
@ -415,8 +424,8 @@ class ParentPanelController: NSWindowController {
if let note = object?.note, note.isEmpty == false { if let note = object?.note, note.isEmpty == false {
newHeight += 20 newHeight += 20
} else if DataStore.shared().shouldDisplay(.dstTransitionInfo), } else if DataStore.shared().shouldDisplay(.dstTransitionInfo),
let obj = object, let obj = object,
TimezoneDataOperations(with: obj).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) != nil { TimezoneDataOperations(with: obj).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) != nil {
newHeight += 20 newHeight += 20
} }
} }
@ -573,7 +582,7 @@ class ParentPanelController: NSWindowController {
Logger.log(object: nil, for: "Deleted Timezone Through Swipe") Logger.log(object: nil, for: "Deleted Timezone Through Swipe")
} }
private lazy var menubarTitleHandler = MenubarHandler() private lazy var menubarTitleHandler = MenubarTitleProvider()
@objc func updateTime() { @objc func updateTime() {
let store = DataStore.shared() let store = DataStore.shared()
@ -603,8 +612,8 @@ class ParentPanelController: NSWindowController {
let current = preferences[$0] let current = preferences[$0]
if $0 < mainTableView.numberOfRows, if $0 < mainTableView.numberOfRows,
let cellView = mainTableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? TimezoneCellView, let cellView = mainTableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? TimezoneCellView,
let model = TimezoneData.customObject(from: current) { let model = TimezoneData.customObject(from: current) {
if let futureSliderCell = futureSlider.cell as? CustomSliderCell, futureSliderCell.tracking == true { if let futureSliderCell = futureSlider.cell as? CustomSliderCell, futureSliderCell.tracking == true {
return return
} }
@ -624,7 +633,7 @@ class ParentPanelController: NSWindowController {
if let note = model.note, !note.isEmpty { if let note = model.note, !note.isEmpty {
cellView.noteLabel.stringValue = note cellView.noteLabel.stringValue = note
} else if DataStore.shared().shouldDisplay(.dstTransitionInfo), } else if DataStore.shared().shouldDisplay(.dstTransitionInfo),
let value = TimezoneDataOperations(with: model).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) { let value = TimezoneDataOperations(with: model).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) {
cellView.noteLabel.stringValue = value cellView.noteLabel.stringValue = value
} else { } else {
cellView.noteLabel.stringValue = CLEmptyString cellView.noteLabel.stringValue = CLEmptyString
@ -785,7 +794,7 @@ class ParentPanelController: NSWindowController {
sharedDelegate.setupFloatingWindow(false) sharedDelegate.setupFloatingWindow(false)
} else { } else {
sharedDelegate.setupFloatingWindow(true) sharedDelegate.setupFloatingWindow(true)
sharedDelegate.setPanelDefaults() updateDefaultPreferences()
} }
let mode = inverseSelection.isEqual(to: NSNumber(value: 1)) ? "Floating Mode" : "Menubar Mode" let mode = inverseSelection.isEqual(to: NSNumber(value: 1)) ? "Floating Mode" : "Menubar Mode"
@ -815,7 +824,7 @@ class ParentPanelController: NSWindowController {
if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false { if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
if self.upcomingEventCollectionView != nil, if self.upcomingEventCollectionView != nil,
let upcomingEvents = eventCenter.upcomingEventsForDay(events) { let upcomingEvents = eventCenter.upcomingEventsForDay(events) {
self.upcomingEventsDataSource.updateEventsDataSource(upcomingEvents) self.upcomingEventsDataSource.updateEventsDataSource(upcomingEvents)
self.upcomingEventCollectionView.reloadData() self.upcomingEventCollectionView.reloadData()
return return
@ -895,7 +904,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)
@ -931,7 +940,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [ let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!, NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
] ]
if leftButton.attributedTitle.string == "Not Really" { if leftButton.attributedTitle.string == "Not Really" {
@ -982,7 +991,7 @@ class ParentPanelController: NSWindowController {
@objc func openCrowdin() { @objc func openCrowdin() {
guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink), guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink),
let languageCode = Locale.preferredLanguages.first else { return } let languageCode = Locale.preferredLanguages.first else { return }
NSWorkspace.shared.open(localizationURL) NSWorkspace.shared.open(localizationURL)
@ -996,7 +1005,7 @@ class ParentPanelController: NSWindowController {
NSWorkspace.shared.open(sourceURL) NSWorkspace.shared.open(sourceURL)
} }
@objc func openFAQs() { @objc func openFAQs() {
guard let sourceURL = URL(string: AboutUsConstants.FAQsLink) else { return } guard let sourceURL = URL(string: AboutUsConstants.FAQsLink) else { return }
@ -1077,19 +1086,19 @@ extension ParentPanelController: NSSharingServicePickerDelegate {
/// London - 01:17:01 /// London - 01:17:01
private func retrieveAllTimes() -> String { private func retrieveAllTimes() -> String {
var clipboardCopy = String() var clipboardCopy = String()
// Get all timezones // Get all timezones
let timezones = DataStore.shared().timezones() let timezones = DataStore.shared().timezones()
if timezones.isEmpty { if timezones.isEmpty {
return clipboardCopy return clipboardCopy
} }
// Sort them in ascending order // Sort them in ascending order
let sortedByTime = timezones.sorted { obj1, obj2 -> Bool in let sortedByTime = timezones.sorted { obj1, obj2 -> Bool in
let system = NSTimeZone.system let system = NSTimeZone.system
guard let object1 = TimezoneData.customObject(from: obj1), guard let object1 = TimezoneData.customObject(from: obj1),
let object2 = TimezoneData.customObject(from: obj2) let object2 = TimezoneData.customObject(from: obj2)
else { else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return false return false
@ -1103,19 +1112,19 @@ extension ParentPanelController: NSSharingServicePickerDelegate {
return difference1 > difference2 return difference1 > difference2
} }
// Grab date in first place and store it as local variable // Grab date in first place and store it as local variable
guard let earliestTimezone = TimezoneData.customObject(from: sortedByTime.first) else { guard let earliestTimezone = TimezoneData.customObject(from: sortedByTime.first) else {
return clipboardCopy return clipboardCopy
} }
let timezoneOperations = TimezoneDataOperations(with: earliestTimezone) let timezoneOperations = TimezoneDataOperations(with: earliestTimezone)
var sectionTitle = timezoneOperations.todaysDate(with: 0) // TODO: Take slider value into consideration var sectionTitle = timezoneOperations.todaysDate(with: 0) // TODO: Take slider value into consideration
clipboardCopy.append("\(sectionTitle)\n") clipboardCopy.append("\(sectionTitle)\n")
stride(from: 0, to: sortedByTime.count, by: 1).forEach { stride(from: 0, to: sortedByTime.count, by: 1).forEach {
if $0 < sortedByTime.count, if $0 < sortedByTime.count,
let dataModel = TimezoneData.customObject(from: sortedByTime[$0]) { let dataModel = TimezoneData.customObject(from: sortedByTime[$0]) {
let dataOperations = TimezoneDataOperations(with: dataModel) let dataOperations = TimezoneDataOperations(with: dataModel)
let date = dataOperations.todaysDate(with: 0) let date = dataOperations.todaysDate(with: 0)
let time = dataOperations.time(with: 0) let time = dataOperations.time(with: 0)

4
Clocker/Panel/Rate Controller/ReviewController.swift

@ -42,12 +42,12 @@ final class ReviewController {
// 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 3 months // Minimum interval between two versions should be 3 months

2
Clocker/Panel/UI/PanelTableView.swift

@ -32,7 +32,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

6
Clocker/Panel/UI/TimezoneCellView.swift

@ -39,7 +39,7 @@ class TimezoneCellView: NSTableCellView {
func setupLayout() { func setupLayout() {
guard let relativeFont = relativeDate.font, guard let relativeFont = relativeDate.font,
let sunriseFont = sunriseSetTime.font let sunriseFont = sunriseSetTime.font
else { else {
assertionFailure("Unable to convert to NSString") assertionFailure("Unable to convert to NSString")
return return
@ -128,7 +128,7 @@ class TimezoneCellView: NSTableCellView {
} }
guard let customFont = customName.font, guard let customFont = customName.font,
let timeFont = time.font let timeFont = time.font
else { else {
assertionFailure("User Font Size is in unexpectedly nil") assertionFailure("User Font Size is in unexpectedly nil")
return return
@ -165,7 +165,7 @@ class TimezoneCellView: NSTableCellView {
} }
guard let panelTableView = searchView as? PanelTableView, guard let panelTableView = searchView as? PanelTableView,
let enclosingScroller = panelTableView.enclosingScrollView let enclosingScroller = panelTableView.enclosingScrollView
else { else {
// We might be coming from the preview tableview! // We might be coming from the preview tableview!
return return

2
Clocker/Panel/UI/TimezoneDataSource.swift

@ -144,7 +144,7 @@ extension TimezoneDataSource: NSTableViewDataSource, NSTableViewDelegate {
panelController.deleteTimezone(at: row) panelController.deleteTimezone(at: row)
} }
}) })
if #available(OSX 11.0, *) { if #available(OSX 11.0, *) {
swipeToDelete.image = Themer.shared().filledTrashImage() swipeToDelete.image = Themer.shared().filledTrashImage()

6
Clocker/Preferences/About/AboutViewController.swift

@ -99,7 +99,7 @@ class AboutViewController: ParentViewController {
@IBAction func openMyTwitter(_: Any) { @IBAction func openMyTwitter(_: Any) {
guard let twitterURL = URL(string: AboutUsConstants.TwitterLink), guard let twitterURL = URL(string: AboutUsConstants.TwitterLink),
let countryCode = Locale.autoupdatingCurrent.regionCode else { return } let countryCode = Locale.autoupdatingCurrent.regionCode else { return }
NSWorkspace.shared.open(twitterURL) NSWorkspace.shared.open(twitterURL)
@ -110,7 +110,7 @@ class AboutViewController: ParentViewController {
@IBAction func viewSource(_: Any) { @IBAction func viewSource(_: Any) {
guard let sourceURL = URL(string: AboutUsConstants.AppStoreLink), guard let sourceURL = URL(string: AboutUsConstants.AppStoreLink),
let countryCode = Locale.autoupdatingCurrent.regionCode else { return } let countryCode = Locale.autoupdatingCurrent.regionCode else { return }
NSWorkspace.shared.open(sourceURL) NSWorkspace.shared.open(sourceURL)
@ -132,7 +132,7 @@ class AboutViewController: ParentViewController {
@IBAction func openGitHub(_: Any) { @IBAction func openGitHub(_: Any) {
guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink), guard let localizationURL = URL(string: AboutUsConstants.CrowdInLocalizationLink),
let languageCode = Locale.preferredLanguages.first else { return } let languageCode = Locale.preferredLanguages.first else { return }
NSWorkspace.shared.open(localizationURL) NSWorkspace.shared.open(localizationURL)

14
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

@ -139,9 +139,9 @@ class AppFeedbackWindowController: NSWindowController {
let preferences = DataStore.shared().timezones() let preferences = DataStore.shared().timezones()
guard let theme = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber, guard let theme = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber,
let displayFutureSliderKey = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber, let displayFutureSliderKey = DataStore.shared().retrieve(key: CLThemeKey) as? NSNumber,
let relativeDateKey = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber, let relativeDateKey = DataStore.shared().retrieve(key: CLRelativeDateKey) as? NSNumber,
let country = Locale.autoupdatingCurrent.regionCode let country = Locale.autoupdatingCurrent.regionCode
else { else {
return "Error" return "Error"
} }
@ -182,7 +182,7 @@ class AppFeedbackWindowController: NSWindowController {
private func retrieveDataForSending() -> [String: String] { private func retrieveDataForSending() -> [String: String] {
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 let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
else { else {
return [:] return [:]
} }
@ -201,7 +201,7 @@ class AppFeedbackWindowController: NSWindowController {
AppFeedbackConstants.CLOperatingSystemVersion: osVersion, AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo, AppFeedbackConstants.CLClockerVersion: versionInfo,
AppFeedbackConstants.CLAppFeedbackDateProperty: todaysDate(), AppFeedbackConstants.CLAppFeedbackDateProperty: todaysDate(),
AppFeedbackConstants.CLAppFeedbackUserPreferences: generateUserPreferences(), AppFeedbackConstants.CLAppFeedbackUserPreferences: generateUserPreferences()
] ]
} }
@ -285,7 +285,7 @@ class AppFeedbackWindowController: NSWindowController {
@IBAction func navigateToSupportTwitter(_: Any) { @IBAction func navigateToSupportTwitter(_: Any) {
guard let twitterURL = URL(string: AboutUsConstants.TwitterLink), guard let twitterURL = URL(string: AboutUsConstants.TwitterLink),
let countryCode = Locale.autoupdatingCurrent.regionCode else { return } let countryCode = Locale.autoupdatingCurrent.regionCode else { return }
NSWorkspace.shared.open(twitterURL) NSWorkspace.shared.open(twitterURL)
@ -311,7 +311,7 @@ extension AppFeedbackWindowController: NSWindowDelegate {
func bringPreferencesWindowToFront() { func bringPreferencesWindowToFront() {
let windows = NSApplication.shared.windows let windows = NSApplication.shared.windows
let prefWindow = windows.first(where: { window in let prefWindow = windows.first(where: { window in
return window.identifier == NSUserInterfaceItemIdentifier("Preferences") window.identifier == NSUserInterfaceItemIdentifier("Preferences")
}) })
if let prefW = prefWindow { if let prefW = prefWindow {
prefW.makeKeyAndOrderFront(self) prefW.makeKeyAndOrderFront(self)

4
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -60,7 +60,7 @@ class AppearanceViewController: ParentViewController {
"4 days", "4 days",
"5 days", "5 days",
"6 days", "6 days",
"7 days", "7 days"
]) ])
if #available(macOS 11.0, *) {} else { if #available(macOS 11.0, *) {} else {
@ -188,7 +188,7 @@ class AppearanceViewController: ParentViewController {
refresh(panel: true, floating: true) refresh(panel: true, floating: true)
if let selectedFormat = sender.selectedItem?.title, if let selectedFormat = sender.selectedItem?.title,
selectedFormat.contains("ss") { selectedFormat.contains("ss") {
Logger.info("Selected format contains timezone format") Logger.info("Selected format contains timezone format")
guard let panelController = PanelController.panel() else { return } guard let panelController = PanelController.panel() else { return }
panelController.pauseTimer() panelController.pauseTimer()

7
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -84,7 +84,6 @@ class CalendarViewController: ParentViewController {
verifyCalendarAccess() verifyCalendarAccess()
showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1 showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1
showNextMeetingInMenubarControl.isEnabled = DataStore.shared().shouldDisplay(.menubarCompactMode) ? false : true
} }
private func verifyCalendarAccess() { private func verifyCalendarAccess() {
@ -114,7 +113,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)
@ -243,13 +242,13 @@ extension CalendarViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor _: 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
return message return message
} }
if let currentSource = calendars[row] as? CalendarInfo, if let currentSource = calendars[row] as? CalendarInfo,
let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell { let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell {
calendarCell.calendarName.stringValue = currentSource.calendar.title calendarCell.calendarName.stringValue = currentSource.calendar.title
calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off
calendarCell.calendarSelected.target = self calendarCell.calendarSelected.target = self

10
Clocker/Preferences/General/PreferencesDataSource.swift

@ -104,7 +104,7 @@ extension PreferencesDataSource: NSTableViewDataSource {
var selectedDataSource: TimezoneData? var selectedDataSource: TimezoneData?
if selectedTimezones.count > row, if selectedTimezones.count > row,
let model = TimezoneData.customObject(from: selectedTimezones[row]) { let model = TimezoneData.customObject(from: selectedTimezones[row]) {
selectedDataSource = model selectedDataSource = model
} }
@ -161,9 +161,9 @@ extension PreferencesDataSource: NSTableViewDataSource {
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)
@ -174,9 +174,9 @@ extension PreferencesDataSource: NSTableViewDataSource {
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")
} }
} }

41
Clocker/Preferences/General/PreferencesViewController.swift

@ -19,7 +19,6 @@ struct PreferencesConstants {
} }
class PreferencesViewController: ParentViewController { class PreferencesViewController: ParentViewController {
@IBOutlet private var placeholderLabel: NSTextField! @IBOutlet private var placeholderLabel: NSTextField!
@IBOutlet private var timezoneTableView: NSTableView! @IBOutlet private var timezoneTableView: NSTableView!
@IBOutlet private var availableTimezoneTableView: NSTableView! @IBOutlet private var availableTimezoneTableView: NSTableView!
@ -43,14 +42,14 @@ class PreferencesViewController: ParentViewController {
@IBOutlet var startAtLoginLabel: NSTextField! @IBOutlet var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton! @IBOutlet var startupCheckbox: NSButton!
// Sorting // Sorting
private var arePlacesSortedInAscendingOrder = false private var arePlacesSortedInAscendingOrder = false
private var arePlacesSortedInAscendingTimezoneOrder = false private var arePlacesSortedInAscendingTimezoneOrder = false
private var isTimezoneSortOptionSelected = false private var isTimezoneSortOptionSelected = false
private var isTimezoneNameSortOptionSelected = false private var isTimezoneNameSortOptionSelected = false
private var isLabelOptionSelected = false private var isLabelOptionSelected = false
private var isActivityInProgress = false { private var isActivityInProgress = false {
didSet { didSet {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
@ -73,23 +72,19 @@ class PreferencesViewController: ParentViewController {
private lazy var startupManager = StartupManager() private lazy var startupManager = StartupManager()
private var dataTask: URLSessionDataTask? = .none private var dataTask: URLSessionDataTask? = .none
private lazy var notimezoneView: NoTimezoneView? = { private lazy var notimezoneView: NoTimezoneView? = NoTimezoneView(frame: tableview.frame)
NoTimezoneView(frame: tableview.frame)
}()
private var geocodingKey: String = { private var geocodingKey: String = {
guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"), guard let path = Bundle.main.path(forResource: "Keys", ofType: "plist"),
let dictionary = NSDictionary(contentsOfFile: path), let dictionary = NSDictionary(contentsOfFile: path),
let apiKey = dictionary["GeocodingKey"] as? String let apiKey = dictionary["GeocodingKey"] as? String
else { else {
assertionFailure("Unable to find the API key") // assertionFailure("Unable to find the API key")
return "" return ""
} }
return apiKey return apiKey
}() }()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -97,7 +92,7 @@ class PreferencesViewController: ParentViewController {
selector: #selector(refreshTimezoneTableView), selector: #selector(refreshTimezoneTableView),
name: NSNotification.Name.customLabelChanged, name: NSNotification.Name.customLabelChanged,
object: nil) object: nil)
NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification, NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: self, object: self,
queue: OperationQueue.main) { [weak self] _ in queue: OperationQueue.main) { [weak self] _ in
@ -253,7 +248,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)!
]) ])
} }
@ -349,9 +344,9 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
for: "favouriteRemoved") for: "favouriteRemoved")
if let appDelegate = NSApplication.shared.delegate as? AppDelegate, if let appDelegate = NSApplication.shared.delegate as? AppDelegate,
let menubarFavourites = DataStore.shared().menubarTimezones(), let menubarFavourites = DataStore.shared().menubarTimezones(),
menubarFavourites.isEmpty, menubarFavourites.isEmpty,
DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false { DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false {
appDelegate.invalidateMenubarTimer(true) appDelegate.invalidateMenubarTimer(true)
} }
@ -477,7 +472,7 @@ extension PreferencesViewController {
self.prepareUIForPresentingResults() self.prepareUIForPresentingResults()
} }
}) })
} }
} }
@ -515,7 +510,7 @@ 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]
finalResults.append(TimezoneData(with: totalPackage)) finalResults.append(TimezoneData(with: totalPackage))
@ -607,7 +602,7 @@ extension PreferencesViewController {
"latitude": dataObject.latitude!, "latitude": dataObject.latitude!,
"longitude": dataObject.longitude!, "longitude": dataObject.longitude!,
"nextUpdate": CLEmptyString, "nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress, CLCustomLabel: filteredAddress
] as [String: Any] ] as [String: Any]
// Mark if the timezone is same as local timezone // Mark if the timezone is same as local timezone
@ -895,7 +890,7 @@ extension PreferencesViewController {
let system = NSTimeZone.system let system = NSTimeZone.system
guard let object1 = TimezoneData.customObject(from: obj1), guard let object1 = TimezoneData.customObject(from: obj1),
let object2 = TimezoneData.customObject(from: obj2) let object2 = TimezoneData.customObject(from: obj2)
else { else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return false return false
@ -923,7 +918,7 @@ extension PreferencesViewController {
let sortedLabels = selectedTimeZones.sorted { obj1, obj2 -> Bool in let sortedLabels = selectedTimeZones.sorted { obj1, obj2 -> Bool in
guard let object1 = TimezoneData.customObject(from: obj1), guard let object1 = TimezoneData.customObject(from: obj1),
let object2 = TimezoneData.customObject(from: obj2) let object2 = TimezoneData.customObject(from: obj2)
else { else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return false return false
@ -947,7 +942,7 @@ extension PreferencesViewController {
let sortedByAddress = selectedTimeZones.sorted { obj1, obj2 -> Bool in let sortedByAddress = selectedTimeZones.sorted { obj1, obj2 -> Bool in
guard let object1 = TimezoneData.customObject(from: obj1), guard let object1 = TimezoneData.customObject(from: obj1),
let object2 = TimezoneData.customObject(from: obj2) let object2 = TimezoneData.customObject(from: obj2)
else { else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return false return false
@ -1014,7 +1009,7 @@ extension PreferencesViewController: PreferenceSelectionUpdates {
let sortedTimezones = selectedTimeZones.sorted { obj1, obj2 -> Bool in let sortedTimezones = selectedTimeZones.sorted { obj1, obj2 -> Bool in
guard let object1 = TimezoneData.customObject(from: obj1), guard let object1 = TimezoneData.customObject(from: obj1),
let object2 = TimezoneData.customObject(from: obj2) let object2 = TimezoneData.customObject(from: obj2)
else { else {
assertionFailure("Data was unexpectedly nil") assertionFailure("Data was unexpectedly nil")
return false return false

5
Clocker/Preferences/Menu Bar/MenubarHandler.swift → Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift

@ -5,7 +5,7 @@ import CoreLoggerKit
import CoreModelKit import CoreModelKit
import EventKit import EventKit
class MenubarHandler: NSObject { class MenubarTitleProvider: NSObject {
func titleForMenubar() -> String? { func titleForMenubar() -> String? {
if let nextEvent = checkForUpcomingEvents() { if let nextEvent = checkForUpcomingEvents() {
return nextEvent return nextEvent
@ -34,7 +34,7 @@ class MenubarHandler: NSObject {
return nil return nil
} }
private func checkForUpcomingEvents() -> String? { 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
@ -48,7 +48,6 @@ class MenubarHandler: NSObject {
if timeForEventToStart > 30 { if timeForEventToStart > 30 {
Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins") Logger.info("Our next event: \(event.event.title ?? "Error") starts in \(timeForEventToStart) mins")
continue continue
} }

68
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -18,6 +18,10 @@ func bufferCalculatedWidth() -> Int {
if DataStore.shared().shouldShowDateInMenubar() { if DataStore.shared().shouldShowDateInMenubar() {
totalWidth += 20 totalWidth += 20
} }
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
totalWidth += 100
}
return totalWidth return totalWidth
} }
@ -55,6 +59,14 @@ func compactWidth(for timezone: TimezoneData) -> Int {
// Test with Sat 12:46 AM // Test with Sat 12:46 AM
let bufferWidth: CGFloat = 9.5 let bufferWidth: CGFloat = 9.5
protocol StatusItemViewConforming {
/// Mark that we need to refresh the text we're showing in the menubar
func statusItemViewSetNeedsDisplay()
/// Status Item Views can be used to represent different information (like time in location, or an upcoming meeting). Distinguish between different status items view through this identifier
func statusItemViewIdentifier() -> String
}
class StatusContainerView: NSView { class StatusContainerView: NSView {
private var previousX: Int = 0 private var previousX: Int = 0
@ -64,8 +76,20 @@ class StatusContainerView: NSView {
layer?.backgroundColor = NSColor.clear.cgColor layer?.backgroundColor = NSColor.clear.cgColor
} }
init(with timezones: [Data]) { init(with timezones: [Data], showUpcomingEventView: Bool) {
func addSubviews() { func addSubviews() {
if showUpcomingEventView,
let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())],
events.isEmpty == false,
let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events) {
let calculatedWidth = bestWidth(for: upcomingEvent)
let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30)
let calendarItemView = UpcomingEventStatusItemView(frame: frame)
calendarItemView.dataObject = upcomingEvent
addSubview(calendarItemView)
previousX += calculatedWidth
}
timezones.forEach { timezones.forEach {
if let timezoneObject = TimezoneData.customObject(from: $0) { if let timezoneObject = TimezoneData.customObject(from: $0) {
addTimezone(timezoneObject) addTimezone(timezoneObject)
@ -76,11 +100,11 @@ class StatusContainerView: NSView {
let timeBasedAttributes = [ let timeBasedAttributes = [
NSAttributedString.Key.font: compactModeTimeFont, NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.backgroundColor: NSColor.clear, NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle, NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
] ]
func containerWidth(for timezones: [Data]) -> CGFloat { func containerWidth(for timezones: [Data]) -> CGFloat {
let compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in var compressedWidth = timezones.reduce(0.0) { result, timezone -> CGFloat in
if let timezoneObject = TimezoneData.customObject(from: timezone) { if let timezoneObject = TimezoneData.customObject(from: timezone) {
let precalculatedWidth = Double(compactWidth(for: timezoneObject)) let precalculatedWidth = Double(compactWidth(for: timezoneObject))
@ -95,6 +119,10 @@ class StatusContainerView: NSView {
return result + CGFloat(bufferCalculatedWidth()) return result + CGFloat(bufferCalculatedWidth())
} }
if showUpcomingEventView {
compressedWidth += 70
}
let calculatedWidth = min(compressedWidth, let calculatedWidth = min(compressedWidth,
CGFloat(timezones.count * bufferCalculatedWidth())) CGFloat(timezones.count * bufferCalculatedWidth()))
return calculatedWidth return calculatedWidth
@ -135,7 +163,7 @@ class StatusContainerView: NSView {
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
] ]
let operation = TimezoneDataOperations(with: timezone) let operation = TimezoneDataOperations(with: timezone)
@ -145,11 +173,41 @@ class StatusContainerView: NSView {
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth) return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)
} }
private func bestWidth(for eventInfo: EventInfo) -> Int {
var textColor = hasDarkAppearance ? NSColor.white : NSColor.black
if #available(OSX 11.0, *) {
textColor = NSColor.white
}
let timeBasedAttributes = [
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
]
let bestSize = compactModeTimeFont.size(eventInfo.metadataForMeeting(),
55, // Default for a location based status view
attributes: timeBasedAttributes)
let bestTitleSize = compactModeTimeFont.size(eventInfo.event.title,
70, // Little more buffer since meeting titles tend to be longer
attributes: timeBasedAttributes)
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)
}
func updateTime() { func updateTime() {
if subviews.isEmpty { if subviews.isEmpty {
assertionFailure("Subviews count should > 0") assertionFailure("Subviews count should > 0")
} }
for view in subviews {
if let conformingView = view as? StatusItemViewConforming {
conformingView.statusItemViewSetNeedsDisplay()
}
}
// See if frame's width needs any adjustment // See if frame's width needs any adjustment
adjustWidthIfNeccessary() adjustWidthIfNeccessary()
@ -170,8 +228,6 @@ class StatusContainerView: NSView {
y: statusItem.frame.origin.y, y: statusItem.frame.origin.y,
width: newBestWidth, width: newBestWidth,
height: statusItem.frame.size.height) height: statusItem.frame.size.height)
statusItem.updateTimeInMenubar()
} }
} }

52
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -22,9 +22,9 @@ class StatusItemHandler: NSObject {
return statusItem return statusItem
}() }()
private var menubarTitleHandler = MenubarHandler() private var menubarTitleHandler = MenubarTitleProvider()
private var parentView: StatusContainerView? private var statusContainerView: StatusContainerView?
private var nsCalendar = Calendar.autoupdatingCurrent private var nsCalendar = Calendar.autoupdatingCurrent
@ -42,7 +42,7 @@ class StatusItemHandler: NSObject {
switch oldValue { switch oldValue {
case .compactText: case .compactText:
statusItem.view = nil statusItem.view = nil
parentView = nil statusContainerView = nil
case .standardText: case .standardText:
statusItem.button?.title = CLEmptyString statusItem.button?.title = CLEmptyString
case .icon: case .icon:
@ -118,12 +118,12 @@ class StatusItemHandler: NSObject {
queue: mainQueue) { _ in queue: mainQueue) { _ in
self.setupStatusItem() self.setupStatusItem()
} }
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: OperationQueue.main) { notification in NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: OperationQueue.main) { _ in
self.menubarTimer?.invalidate() self.menubarTimer?.invalidate()
} }
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: OperationQueue.main) { notification in NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: OperationQueue.main) { _ in
self.setupStatusItem() self.setupStatusItem()
} }
} }
@ -134,8 +134,8 @@ class StatusItemHandler: NSObject {
} }
} }
private func constructCompactView() { private func constructCompactView(with upcomingEventView: Bool = false) {
parentView = nil statusContainerView = nil
let menubarTimezones = DataStore.shared().menubarTimezones() ?? [] let menubarTimezones = DataStore.shared().menubarTimezones() ?? []
if menubarTimezones.isEmpty { if menubarTimezones.isEmpty {
@ -143,8 +143,9 @@ class StatusItemHandler: NSObject {
return return
} }
parentView = StatusContainerView(with: menubarTimezones) statusContainerView = StatusContainerView(with: menubarTimezones,
statusItem.view = parentView showUpcomingEventView: upcomingEventView)
statusItem.view = statusContainerView
statusItem.view?.window?.backgroundColor = NSColor.clear statusItem.view?.window?.backgroundColor = NSColor.clear
} }
@ -183,7 +184,7 @@ class StatusItemHandler: NSObject {
if let strongSelf = self { if let strongSelf = self {
strongSelf.refresh() strongSelf.refresh()
} }
}) })
// Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval // Tolerance, even a small amount, has a positive imapct on the power usage. As a rule, we set it to 10% of the interval
menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20 menubarTimer?.tolerance = shouldDisplaySeconds ? 0.5 : 20
@ -241,7 +242,20 @@ class StatusItemHandler: NSObject {
} }
func updateCompactMenubar() { func updateCompactMenubar() {
parentView?.updateTime() if let upcomingEvent = menubarTitleHandler.checkForUpcomingEvents() {
print("Need to construct upcoming event view \(upcomingEvent)")
// Iterate and see if we're showing the calendar item view
let upcomingEventView = retrieveUpcomingEventStatusView()
// If not, reconstruct Status Container View with another view
if upcomingEventView == nil {
constructCompactView(with: true)
}
} else {
let upcomingEventView = retrieveUpcomingEventStatusView()
upcomingEventView?.removeFromSuperview()
}
// This will internally call `statusItemViewSetNeedsDisplay` on all subviews ensuring all text in the menubar is up-to-date.
statusContainerView?.updateTime()
} }
func refresh() { func refresh() {
@ -347,7 +361,17 @@ class StatusItemHandler: NSObject {
menubarTimer?.invalidate() menubarTimer?.invalidate()
menubarTimer = nil menubarTimer = nil
constructCompactView() constructCompactView(with: menubarTitleHandler.checkForUpcomingEvents() != nil)
updateMenubar() updateMenubar()
} }
private func retrieveUpcomingEventStatusView() -> NSView? {
let upcomingEventView = statusContainerView?.subviews.first(where: { statusItemView in
if let upcomingEventView = statusItemView as? StatusItemViewConforming {
return upcomingEventView.statusItemViewIdentifier() == "upcoming_event_view"
}
return false
})
return upcomingEventView
}
} }

26
Clocker/Preferences/Menu Bar/StatusItemView.swift

@ -50,7 +50,7 @@ class StatusItemView: NSView {
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
} }
@ -62,7 +62,7 @@ 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
} }
@ -91,26 +91,21 @@ 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)
]) ])
} }
@available(OSX 10.14, *) @available(OSX 10.14, *)
override func viewDidChangeEffectiveAppearance() { override func viewDidChangeEffectiveAppearance() {
super.viewDidChangeEffectiveAppearance() super.viewDidChangeEffectiveAppearance()
updateTimeInMenubar() statusItemViewSetNeedsDisplay()
}
func updateTimeInMenubar() {
locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes)
timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes)
} }
private func initialSetup() { private func initialSetup() {
@ -132,3 +127,14 @@ class StatusItemView: NSView {
mainDelegate.togglePanel(event) mainDelegate.togglePanel(event)
} }
} }
extension StatusItemView: StatusItemViewConforming {
func statusItemViewSetNeedsDisplay() {
locationView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuTitle(), attributes: textFontAttributes)
timeView.attributedStringValue = NSAttributedString(string: operationsObject.compactMenuSubtitle(), attributes: timeAttributes)
}
func statusItemViewIdentifier() -> String {
return "location_view"
}
}

28
Clocker/Preferences/Menu Bar/UpcomingEventStatusItemView.swift

@ -21,7 +21,7 @@ class UpcomingEventStatusItemView: NSView {
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
} }
@ -33,7 +33,7 @@ class UpcomingEventStatusItemView: 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
} }
@ -54,26 +54,33 @@ class UpcomingEventStatusItemView: NSView {
nextEventField.leadingAnchor.constraint(equalTo: leadingAnchor), nextEventField.leadingAnchor.constraint(equalTo: leadingAnchor),
nextEventField.trailingAnchor.constraint(equalTo: trailingAnchor), nextEventField.trailingAnchor.constraint(equalTo: trailingAnchor),
nextEventField.topAnchor.constraint(equalTo: topAnchor, constant: 7), nextEventField.topAnchor.constraint(equalTo: topAnchor, constant: 7),
nextEventField.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35), nextEventField.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35)
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
etaField.leadingAnchor.constraint(equalTo: leadingAnchor), etaField.leadingAnchor.constraint(equalTo: leadingAnchor),
etaField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), etaField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
etaField.topAnchor.constraint(equalTo: nextEventField.bottomAnchor), etaField.topAnchor.constraint(equalTo: nextEventField.bottomAnchor),
etaField.bottomAnchor.constraint(equalTo: bottomAnchor), etaField.bottomAnchor.constraint(equalTo: bottomAnchor)
]) ])
} }
@available(*, unavailable)
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
private func initialSetup() { private func initialSetup() {
nextEventField.attributedStringValue = NSAttributedString(string: "Next Event", attributes: textFontAttributes) nextEventField.attributedStringValue = NSAttributedString(string: dataObject.event.title, attributes: textFontAttributes)
etaField.attributedStringValue = NSAttributedString(string: dataObject.metadataForMeeting(), attributes: timeAttributes) etaField.attributedStringValue = NSAttributedString(string: dataObject.metadataForMeeting(), attributes: timeAttributes)
} }
@available(OSX 10.14, *)
override func viewDidChangeEffectiveAppearance() {
super.viewDidChangeEffectiveAppearance()
statusItemViewSetNeedsDisplay()
}
func updateWithNextEventInfo(_ metadata: String) { func updateWithNextEventInfo(_ metadata: String) {
nextEventField.attributedStringValue = NSAttributedString(string: "Next Event", attributes: textFontAttributes) nextEventField.attributedStringValue = NSAttributedString(string: "Next Event", attributes: textFontAttributes)
etaField.attributedStringValue = NSAttributedString(string: metadata, attributes: timeAttributes) etaField.attributedStringValue = NSAttributedString(string: metadata, attributes: timeAttributes)
@ -88,3 +95,14 @@ class UpcomingEventStatusItemView: NSView {
mainDelegate.togglePanel(event) mainDelegate.togglePanel(event)
} }
} }
extension UpcomingEventStatusItemView: StatusItemViewConforming {
func statusItemViewSetNeedsDisplay() {
nextEventField.attributedStringValue = NSAttributedString(string: dataObject.event.title, attributes: textFontAttributes)
etaField.attributedStringValue = NSAttributedString(string: dataObject.metadataForMeeting(), attributes: timeAttributes)
}
func statusItemViewIdentifier() -> String {
return "upcoming_event_view"
}
}

8
Clocker/Preferences/OneWindowController.swift

@ -50,7 +50,7 @@ class OneWindowController: NSWindowController {
window?.backgroundColor = Themer.shared().mainBackgroundColor() window?.backgroundColor = Themer.shared().mainBackgroundColor()
window?.identifier = NSUserInterfaceItemIdentifier("Preferences") window?.identifier = NSUserInterfaceItemIdentifier("Preferences")
} }
private func setupToolbarImages() { private func setupToolbarImages() {
guard let tabViewController = contentViewController as? CenteredTabViewController else { guard let tabViewController = contentViewController as? CenteredTabViewController else {
return return
@ -78,18 +78,18 @@ class OneWindowController: NSWindowController {
} }
// MARK: Public // MARK: Public
func openPermissionsPane() { func openPermissionsPane() {
openPreferenceTab(at: 3) openPreferenceTab(at: 3)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
} }
// Action mapped to the + button in the PanelController. We should always open the General Pane when the + button is clicked. // Action mapped to the + button in the PanelController. We should always open the General Pane when the + button is clicked.
func openGeneralPane() { func openGeneralPane() {
openPreferenceTab(at: 0) openPreferenceTab(at: 0)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
} }
private func openPreferenceTab(at index: Int) { private func openPreferenceTab(at index: Int) {
guard let window = window else { guard let window = window else {
return return

6
Clocker/StartupKit/Package.swift

@ -6,13 +6,13 @@ import PackageDescription
let package = Package( let package = Package(
name: "StartupKit", name: "StartupKit",
platforms: [ platforms: [
.macOS(.v10_12), .macOS(.v10_12)
], ],
products: [ products: [
.library( .library(
name: "StartupKit", name: "StartupKit",
targets: ["StartupKit"] targets: ["StartupKit"]
), )
], ],
dependencies: [ dependencies: [
// Dependencies declare other packages that this package depends on. // Dependencies declare other packages that this package depends on.
@ -22,6 +22,6 @@ let package = Package(
.target( .target(
name: "StartupKit", name: "StartupKit",
dependencies: [] dependencies: []
), )
] ]
) )

Loading…
Cancel
Save