Browse Source

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

pull/113/head
Abhishek Banthia 3 years ago
parent
commit
be5e987df8
  1. 9
      Clocker/AppDelegate.swift
  2. 20
      Clocker/Clocker.xcodeproj/project.pbxproj
  3. 4
      Clocker/Clocker/Clocker.entitlements
  4. 3
      Clocker/Clocker/ar.lproj/Localizable.strings
  5. 5
      Clocker/Clocker/ca.lproj/Localizable.strings
  6. 5
      Clocker/Clocker/de.lproj/Localizable.strings
  7. 5
      Clocker/Clocker/en.lproj/Localizable.strings
  8. 5
      Clocker/Clocker/es.lproj/Localizable.strings
  9. 5
      Clocker/Clocker/fr.lproj/Localizable.strings
  10. 5
      Clocker/Clocker/hi.lproj/Localizable.strings
  11. 5
      Clocker/Clocker/hr.lproj/Localizable.strings
  12. 5
      Clocker/Clocker/it.lproj/Localizable.strings
  13. 5
      Clocker/Clocker/ja.lproj/Localizable.strings
  14. 5
      Clocker/Clocker/ko.lproj/Localizable.strings
  15. 5
      Clocker/Clocker/nl.lproj/Localizable.strings
  16. 3
      Clocker/Clocker/pl.lproj/Localizable.strings
  17. 5
      Clocker/Clocker/pt-BR.lproj/Localizable.strings
  18. 5
      Clocker/Clocker/ru.lproj/Localizable.strings
  19. 3
      Clocker/Clocker/tr.lproj/Localizable.strings
  20. 5
      Clocker/Clocker/zh-Hans.lproj/Localizable.strings
  21. 5
      Clocker/Clocker/zh-Hant.lproj/Localizable.strings
  22. 21
      Clocker/ClockerUnitTests/AppDelegateTests.swift
  23. 4
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  24. 2
      Clocker/ClockerUnitTests/DateFormatterManagerTests.swift
  25. 3
      Clocker/ClockerUnitTests/ReviewControllerTests.swift
  26. 139
      Clocker/ClockerUnitTests/SearchDataSourceTests.swift
  27. 93
      Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift
  28. 6
      Clocker/CoreLoggerKit/Package.swift
  29. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/CoreLoggerKitTests.swift
  30. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/XCTestManifests.swift
  31. 8
      Clocker/CoreModelKit/Package.swift
  32. 2
      Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift
  33. 2
      Clocker/CoreModelKit/Tests/CoreModelKitTests/XCTestManifests.swift
  34. 43
      Clocker/Dependencies/Date Additions/Date+Inits.swift
  35. 667
      Clocker/Dependencies/Date Additions/TimePeriod.swift
  36. 177
      Clocker/Dependencies/Date Additions/TimePeriodChain.swift
  37. 267
      Clocker/Dependencies/Date Additions/TimePeriodCollection.swift
  38. 146
      Clocker/Dependencies/Date Additions/TimePeriodGroup.swift
  39. 11
      Clocker/Events and Reminders/CalendarHandler.swift
  40. 3
      Clocker/Events and Reminders/RemindersHandler.swift
  41. 10
      Clocker/Menu Bar/StatusContainerView.swift
  42. 3
      Clocker/Menu Bar/StatusItemHandler.swift
  43. 8
      Clocker/Menu Bar/StatusItemView.swift
  44. 7
      Clocker/Onboarding/OnboardingSearchController.swift
  45. 4
      Clocker/Overall App/ConfigExport.swift
  46. 43
      Clocker/Overall App/DataStore.swift
  47. 1
      Clocker/Overall App/DateFormatterManager.swift
  48. 1
      Clocker/Overall App/Strings.swift
  49. 3
      Clocker/Overall App/Themer.swift
  50. 2
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  51. 10
      Clocker/Panel/Notes Popover/NotesPopover.swift
  52. 2
      Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift
  53. 16
      Clocker/Panel/PanelController.swift
  54. 32
      Clocker/Panel/ParentPanelController.swift
  55. 2
      Clocker/Panel/UI/PanelTableView.swift
  56. 4
      Clocker/Panel/UI/TimezoneCellView.swift
  57. 5
      Clocker/Panel/Upcoming Events/UpcomingEventViewItem.swift
  58. 2
      Clocker/Preferences/About/PointingHandCursorButton.swift
  59. 5
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  60. 22
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  61. 27
      Clocker/Preferences/Calendar/CalendarViewController.swift
  62. 7
      Clocker/Preferences/General/PreferencesDataSource.swift
  63. 14
      Clocker/Preferences/General/PreferencesViewController.swift
  64. 10
      Clocker/Preferences/General/SearchDataSource.swift
  65. 13
      Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift
  66. 16
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  67. 26
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift
  68. 8
      Clocker/Preferences/Menu Bar/StatusItemView.swift
  69. 8
      Clocker/Preferences/Menu Bar/UpcomingEventStatusItemView.swift
  70. 90
      Clocker/Preferences/Preferences.storyboard
  71. 6
      Clocker/StartupKit/Package.swift

9
Clocker/AppDelegate.swift

@ -8,7 +8,7 @@ import FirebaseCrashlytics
open class AppDelegate: NSObject, NSApplicationDelegate { open class AppDelegate: NSObject, NSApplicationDelegate {
private lazy var floatingWindow = FloatingWindowController.shared() private lazy var floatingWindow = FloatingWindowController.shared()
private lazy var panelController = PanelController.shared() internal lazy var panelController = PanelController(windowNibName: .panel)
private var statusBarHandler: StatusItemHandler! private var statusBarHandler: StatusItemHandler!
private var panelObserver: NSKeyValueObservation? private var panelObserver: NSKeyValueObservation?
@ -84,7 +84,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
let floatingWindow = FloatingWindowController.shared() let floatingWindow = FloatingWindowController.shared()
floatingWindow.openPreferences(NSButton()) floatingWindow.openPreferences(NSButton())
} else { } else {
let panelController = PanelController.shared()
panelController.openPreferences(NSButton()) panelController.openPreferences(NSButton())
} }
} }
@ -161,12 +160,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
} }
} }
private func showAppAlreadyOpenMessage() {
showAlert(message: "An instance of Clocker is already open 😅",
informativeText: "This instance of Clocker will terminate now.",
buttonTitle: "Close")
}
private func showAlert(message: String, informativeText: String, buttonTitle: String) { private func showAlert(message: String, informativeText: String, buttonTitle: String) {
NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert() let alert = NSAlert()

20
Clocker/Clocker.xcodeproj/project.pbxproj

@ -53,6 +53,7 @@
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 */; }; 35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */; };
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */; }; 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */; };
35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35621CFB27F66C1900926D5C /* SearchDataSourceTests.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 */; };
@ -70,11 +71,8 @@
35C36EF822595F14002FA5C6 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */; }; 35C36EF822595F14002FA5C6 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */; };
35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */; }; 35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */; };
35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFA2259616B002FA5C6 /* Solar.swift */; }; 35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFA2259616B002FA5C6 /* Solar.swift */; };
35C36F0D225961DA002FA5C6 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFD225961D9002FA5C6 /* TimePeriod.swift */; };
35C36F0E225961DA002FA5C6 /* Date+Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */; }; 35C36F0E225961DA002FA5C6 /* Date+Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */; };
35C36F0F225961DA002FA5C6 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFF225961D9002FA5C6 /* TimePeriodCollection.swift */; };
35C36F10225961DA002FA5C6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F00225961D9002FA5C6 /* Constants.swift */; }; 35C36F10225961DA002FA5C6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F00225961D9002FA5C6 /* Constants.swift */; };
35C36F11225961DA002FA5C6 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F01225961D9002FA5C6 /* TimePeriodGroup.swift */; };
35C36F12225961DA002FA5C6 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F02225961DA002FA5C6 /* Date+Components.swift */; }; 35C36F12225961DA002FA5C6 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F02225961DA002FA5C6 /* Date+Components.swift */; };
35C36F13225961DA002FA5C6 /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */; }; 35C36F13225961DA002FA5C6 /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */; };
35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */; }; 35C36F14225961DA002FA5C6 /* Integer+DateTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */; };
@ -85,7 +83,6 @@
35C36F19225961DA002FA5C6 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F09225961DA002FA5C6 /* Enums.swift */; }; 35C36F19225961DA002FA5C6 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F09225961DA002FA5C6 /* Enums.swift */; };
35C36F1A225961DA002FA5C6 /* Date+Manipulations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */; }; 35C36F1A225961DA002FA5C6 /* Date+Manipulations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */; };
35C36F1B225961DA002FA5C6 /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0B225961DA002FA5C6 /* Date+Format.swift */; }; 35C36F1B225961DA002FA5C6 /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0B225961DA002FA5C6 /* Date+Format.swift */; };
35C36F1C225961DA002FA5C6 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0C225961DA002FA5C6 /* TimePeriodChain.swift */; };
35C36F2022596253002FA5C6 /* OneWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1E22596253002FA5C6 /* OneWindowController.swift */; }; 35C36F2022596253002FA5C6 /* OneWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1E22596253002FA5C6 /* OneWindowController.swift */; };
35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */; }; 35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */; };
35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */; }; 35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */; };
@ -291,6 +288,7 @@
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>"; }; 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>"; }; 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateTests.swift; sourceTree = "<group>"; };
35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDataSourceTests.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>"; };
@ -311,11 +309,8 @@
35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; }; 35C36EEF22595F14002FA5C6 /* Onboarding.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; };
35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingParentViewController.swift; sourceTree = "<group>"; }; 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingParentViewController.swift; sourceTree = "<group>"; };
35C36EFA2259616B002FA5C6 /* Solar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Solar.swift; sourceTree = "<group>"; }; 35C36EFA2259616B002FA5C6 /* Solar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Solar.swift; sourceTree = "<group>"; };
35C36EFD225961D9002FA5C6 /* TimePeriod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimePeriod.swift; sourceTree = "<group>"; };
35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Bundle.swift"; sourceTree = "<group>"; }; 35C36EFE225961D9002FA5C6 /* Date+Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Bundle.swift"; sourceTree = "<group>"; };
35C36EFF225961D9002FA5C6 /* TimePeriodCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimePeriodCollection.swift; sourceTree = "<group>"; };
35C36F00225961D9002FA5C6 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; }; 35C36F00225961D9002FA5C6 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
35C36F01225961D9002FA5C6 /* TimePeriodGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimePeriodGroup.swift; sourceTree = "<group>"; };
35C36F02225961DA002FA5C6 /* Date+Components.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Components.swift"; sourceTree = "<group>"; }; 35C36F02225961DA002FA5C6 /* Date+Components.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Components.swift"; sourceTree = "<group>"; };
35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; }; 35C36F03225961DA002FA5C6 /* Date+TimeAgo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Integer+DateTools.swift"; sourceTree = "<group>"; }; 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Integer+DateTools.swift"; sourceTree = "<group>"; };
@ -326,7 +321,6 @@
35C36F09225961DA002FA5C6 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = "<group>"; }; 35C36F09225961DA002FA5C6 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = "<group>"; };
35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Manipulations.swift"; sourceTree = "<group>"; }; 35C36F0A225961DA002FA5C6 /* Date+Manipulations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Manipulations.swift"; sourceTree = "<group>"; };
35C36F0B225961DA002FA5C6 /* Date+Format.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Format.swift"; sourceTree = "<group>"; }; 35C36F0B225961DA002FA5C6 /* Date+Format.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Format.swift"; sourceTree = "<group>"; };
35C36F0C225961DA002FA5C6 /* TimePeriodChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimePeriodChain.swift; sourceTree = "<group>"; };
35C36F1E22596253002FA5C6 /* OneWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneWindowController.swift; sourceTree = "<group>"; }; 35C36F1E22596253002FA5C6 /* OneWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneWindowController.swift; sourceTree = "<group>"; };
35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = "<group>"; }; 35C36F1F22596253002FA5C6 /* AppearanceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = "<group>"; };
35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParentPanelController.swift; sourceTree = "<group>"; }; 35C36F272259D6FA002FA5C6 /* ParentPanelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParentPanelController.swift; sourceTree = "<group>"; };
@ -598,10 +592,6 @@
35C36F09225961DA002FA5C6 /* Enums.swift */, 35C36F09225961DA002FA5C6 /* Enums.swift */,
35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */, 35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */,
35C36F05225961DA002FA5C6 /* TimeChunk.swift */, 35C36F05225961DA002FA5C6 /* TimeChunk.swift */,
35C36EFD225961D9002FA5C6 /* TimePeriod.swift */,
35C36F0C225961DA002FA5C6 /* TimePeriodChain.swift */,
35C36EFF225961D9002FA5C6 /* TimePeriodCollection.swift */,
35C36F01225961D9002FA5C6 /* TimePeriodGroup.swift */,
); );
path = "Date Additions"; path = "Date Additions";
sourceTree = "<group>"; sourceTree = "<group>";
@ -890,6 +880,7 @@
35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */, 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */,
35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */, 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */,
35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */, 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */,
35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */,
); );
path = ClockerUnitTests; path = ClockerUnitTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1279,6 +1270,7 @@
35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */, 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */,
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */, 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */,
9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */, 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */,
35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */,
35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */, 35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */,
C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */, C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */,
); );
@ -1354,7 +1346,6 @@
35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */, 35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */,
35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */, 35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */,
35C36F582259DD8A002FA5C6 /* PanelTableView.swift in Sources */, 35C36F582259DD8A002FA5C6 /* PanelTableView.swift in Sources */,
35C36F0F225961DA002FA5C6 /* TimePeriodCollection.swift in Sources */,
35C36F18225961DA002FA5C6 /* Date+Comparators.swift in Sources */, 35C36F18225961DA002FA5C6 /* Date+Comparators.swift in Sources */,
353B5BC52698B78A0023858D /* UpcomingEventStatusItemView.swift in Sources */, 353B5BC52698B78A0023858D /* UpcomingEventStatusItemView.swift in Sources */,
35C36FA02259ED6D002FA5C6 /* CalendarHandler.swift in Sources */, 35C36FA02259ED6D002FA5C6 /* CalendarHandler.swift in Sources */,
@ -1374,12 +1365,9 @@
9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */, 9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */,
3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */, 3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */,
9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */, 9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */,
35C36F1C225961DA002FA5C6 /* TimePeriodChain.swift in Sources */,
3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */, 3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */,
35C36F11225961DA002FA5C6 /* TimePeriodGroup.swift in Sources */,
35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */, 35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */,
35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */, 35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */,
35C36F0D225961DA002FA5C6 /* TimePeriod.swift in Sources */,
35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */, 35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */,
35C36F662259DF4C002FA5C6 /* UpcomingEventView.swift in Sources */, 35C36F662259DF4C002FA5C6 /* UpcomingEventView.swift in Sources */,
35C36EF522595F14002FA5C6 /* OnboardingSearchController.swift in Sources */, 35C36EF522595F14002FA5C6 /* OnboardingSearchController.swift in Sources */,

4
Clocker/Clocker/Clocker.entitlements

@ -2,10 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.cs.disable-library-validation</key> <key>com.apple.security.cs.disable-library-validation</key>

3
Clocker/Clocker/ar.lproj/Localizable.strings

@ -158,3 +158,6 @@
"New Zealand" = "New Zealand"; "New Zealand" = "New Zealand";
"Florida" = "Florida"; "Florida" = "Florida";
"San Francisco" = "San Francisco"; "San Francisco" = "San Francisco";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/ca.lproj/Localizable.strings

@ -161,6 +161,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/de.lproj/Localizable.strings

@ -161,6 +161,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/en.lproj/Localizable.strings

@ -165,6 +165,9 @@
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
// Upcoming Event View // Upcoming Event View
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/es.lproj/Localizable.strings

@ -163,6 +163,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/fr.lproj/Localizable.strings

@ -162,6 +162,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/hi.lproj/Localizable.strings

@ -157,6 +157,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/hr.lproj/Localizable.strings

@ -163,6 +163,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Prijelaz na ljetno računanje vremena dogodit će se za < 24 sata"; "Daylights Saving transition will occur in < 24 hours" = "Prijelaz na ljetno računanje vremena dogodit će se za < 24 sata";
"Copied to Clipboard" = "Kopirano u međuspremnik"; "Copied to Clipboard" = "Kopirano u međuspremnik";
"No upcoming events for today!" = "Za danas nema predstojećih događaja!"; "No upcoming events for today!" = "Za danas nema predstojećih događaja 🎉";
"Great going." = "Super."; "Great going." = "Super.";
"Happy Weekend." = "Uživaj vikend."; "Happy Weekend." = "Uživaj vikend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/it.lproj/Localizable.strings

@ -162,6 +162,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/ja.lproj/Localizable.strings

@ -192,7 +192,7 @@
"No upcoming event." = "No upcoming event."; "No upcoming event." = "No upcoming event.";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
/* Onboarding */ /* Onboarding */
"Open Clocker At Login" = "ログイン時に Clocker を開始"; "Open Clocker At Login" = "ログイン時に Clocker を開始";
@ -355,3 +355,6 @@
"start-at-login" = "ログイン時に開始"; "start-at-login" = "ログイン時に開始";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/ko.lproj/Localizable.strings

@ -164,6 +164,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉!";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/nl.lproj/Localizable.strings

@ -161,6 +161,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

3
Clocker/Clocker/pl.lproj/Localizable.strings

@ -158,3 +158,6 @@
"New Zealand" = "Nowa Zelandia"; "New Zealand" = "Nowa Zelandia";
"Florida" = "Floryda"; "Florida" = "Floryda";
"San Francisco" = "San Francisco"; "San Francisco" = "San Francisco";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/pt-BR.lproj/Localizable.strings

@ -162,6 +162,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/ru.lproj/Localizable.strings

@ -154,6 +154,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

3
Clocker/Clocker/tr.lproj/Localizable.strings

@ -159,3 +159,6 @@ Takvimleri Görüntüle";
"New Zealand" = "New Zealand"; "New Zealand" = "New Zealand";
"Florida" = "Florida"; "Florida" = "Florida";
"San Francisco" = "San Francisco"; "San Francisco" = "San Francisco";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/zh-Hans.lproj/Localizable.strings

@ -155,6 +155,9 @@
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours"; "Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard"; "Copied to Clipboard" = "Copied to Clipboard";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
"Great going." = "Great going."; "Great going." = "Great going.";
"Happy Weekend." = "Happy Weekend."; "Happy Weekend." = "Happy Weekend.";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

5
Clocker/Clocker/zh-Hant.lproj/Localizable.strings

@ -192,7 +192,7 @@
"No upcoming event." = "沒有即將到來的行程"; "No upcoming event." = "沒有即將到來的行程";
"No upcoming events for today!" = "No upcoming events for today!"; "No upcoming events for today!" = "No upcoming events for today 🎉";
/* Onboarding */ /* Onboarding */
"Open Clocker At Login" = "在登入時打開 Clocker"; "Open Clocker At Login" = "在登入時打開 Clocker";
@ -355,3 +355,6 @@
"start-at-login" = "在登入時啟動"; "start-at-login" = "在登入時啟動";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

21
Clocker/ClockerUnitTests/AppDelegateTests.swift

@ -1,12 +1,11 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
import XCTest
import CoreModelKit import CoreModelKit
import XCTest
@testable import Clocker @testable import Clocker
class AppDelegateTests: XCTestCase { class AppDelegateTests: XCTestCase {
func testStatusItemIsInitialized() throws { func testStatusItemIsInitialized() throws {
let subject = NSApplication.shared.delegate as? AppDelegate let subject = NSApplication.shared.delegate as? AppDelegate
let statusHandler = subject?.statusItemForPanel() let statusHandler = subject?.statusItemForPanel()
@ -49,7 +48,7 @@ class AppDelegateTests: XCTestCase {
subject?.setupFloatingWindow(true) subject?.setupFloatingWindow(true)
let floatingWindow = NSApplication.shared.windows.first { window in let floatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) { if (window.windowController as? FloatingWindowController) != nil {
return true return true
} }
return false return false
@ -61,7 +60,7 @@ class AppDelegateTests: XCTestCase {
subject?.setupFloatingWindow(false) subject?.setupFloatingWindow(false)
let closedFloatingWindow = NSApplication.shared.windows.first { window in let closedFloatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) { if (window.windowController as? FloatingWindowController) != nil {
return true return true
} }
return false return false
@ -137,18 +136,4 @@ class AppDelegateTests: XCTestCase {
UserDefaults.standard.set(0, forKey: CLMenubarCompactMode) // Set the menubar mode back to compact UserDefaults.standard.set(0, forKey: CLMenubarCompactMode) // Set the menubar mode back to compact
} }
func testTogglingPanel() {
UserDefaults.standard.set(1, forKey: CLShowAppInForeground)
let subject = NSApplication.shared.delegate as? AppDelegate
subject?.ping("MockArgument")
UserDefaults.standard.set(0, forKey: CLShowAppInForeground)
let hasActiveGetter = PanelController.shared().hasActivePanel
subject?.ping("MockArgument")
XCTAssertNotEqual(hasActiveGetter, PanelController.shared().hasActivePanel)
}
} }

4
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -173,7 +173,7 @@ class ClockerUnitTests: XCTestCase {
XCTAssertTrue(operations.timeDifference() == ", 9h 30m ahead", "Difference was unexpectedly: \(operations.timeDifference())") XCTAssertTrue(operations.timeDifference() == ", 9h 30m ahead", "Difference was unexpectedly: \(operations.timeDifference())")
XCTAssertTrue(californiaOperations.timeDifference() == ", 3h behind", "Difference was unexpectedly: \(californiaOperations.timeDifference())") XCTAssertTrue(californiaOperations.timeDifference() == ", 3h behind", "Difference was unexpectedly: \(californiaOperations.timeDifference())")
XCTAssertTrue(floridaOperations.timeDifference() == "", "Difference was unexpectedly: \(floridaOperations.timeDifference())") XCTAssertTrue(floridaOperations.timeDifference() == "", "Difference was unexpectedly: \(floridaOperations.timeDifference())")
XCTAssertTrue(aucklandOperations.timeDifference() == ", 17h ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())") XCTAssertTrue(aucklandOperations.timeDifference() == ", 16h ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())")
XCTAssertTrue(omahaOperations.timeDifference() == ", an hour behind", "Difference was unexpectedly: \(omahaOperations.timeDifference())") XCTAssertTrue(omahaOperations.timeDifference() == ", an hour behind", "Difference was unexpectedly: \(omahaOperations.timeDifference())")
} }
@ -401,14 +401,12 @@ class ClockerUnitTests: XCTestCase {
XCTAssertEqual(stringWithComma.filteredName(), "Mumbai") XCTAssertEqual(stringWithComma.filteredName(), "Mumbai")
XCTAssertEqual(stringWithoutComma.filteredName(), "Mumbai") XCTAssertEqual(stringWithoutComma.filteredName(), "Mumbai")
XCTAssertEqual(emptyString.filteredName(), "") XCTAssertEqual(emptyString.filteredName(), "")
} }
func testToasty() { func testToasty() {
let view = NSView(frame: CGRect.zero) let view = NSView(frame: CGRect.zero)
view.makeToast("Hello, this is a toast") view.makeToast("Hello, this is a toast")
XCTAssertEqual(view.subviews.first?.accessibilityIdentifier(), "ToastView") XCTAssertEqual(view.subviews.first?.accessibilityIdentifier(), "ToastView")
let toastExpectation = expectation(description: "Toast View should hide after 1 second") let toastExpectation = expectation(description: "Toast View should hide after 1 second")
let result = XCTWaiter.wait(for: [toastExpectation], timeout: 1.5) // Set 1.5 seconds here for a little leeway let result = XCTWaiter.wait(for: [toastExpectation], timeout: 1.5) // Set 1.5 seconds here for a little leeway
if result == XCTWaiter.Result.timedOut { if result == XCTWaiter.Result.timedOut {

2
Clocker/ClockerUnitTests/DateFormatterManagerTests.swift

@ -5,7 +5,6 @@ import XCTest
@testable import Clocker @testable import Clocker
class DateFormatterManagerTests: XCTestCase { class DateFormatterManagerTests: XCTestCase {
func testRegularDateFormatter() throws { func testRegularDateFormatter() throws {
let subject = DateFormatterManager.dateFormatter(with: .medium, for: "UTC") let subject = DateFormatterManager.dateFormatter(with: .medium, for: "UTC")
XCTAssertEqual(subject.dateStyle, .medium) XCTAssertEqual(subject.dateStyle, .medium)
@ -32,5 +31,4 @@ class DateFormatterManagerTests: XCTestCase {
XCTAssertEqual(subject.timeZone.identifier, "America/Los_Angeles") XCTAssertEqual(subject.timeZone.identifier, "America/Los_Angeles")
XCTAssertEqual(subject.dateFormat, "hh:mm:ss") XCTAssertEqual(subject.dateFormat, "hh:mm:ss")
} }
} }

3
Clocker/ClockerUnitTests/ReviewControllerTests.swift

@ -1,7 +1,7 @@
// Copyright © 2015 Abhishek Banthia // Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker @testable import Clocker
import XCTest
class ReviewControllerTests: XCTestCase { class ReviewControllerTests: XCTestCase {
func testDebuggingMode() throws { func testDebuggingMode() throws {
@ -129,5 +129,4 @@ class ReviewControllerTests: XCTestCase {
XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt")) XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt"))
XCTAssertNotNil(mockDefaults.object(forKey: "last-version")) XCTAssertNotNil(mockDefaults.object(forKey: "last-version"))
} }
} }

139
Clocker/ClockerUnitTests/SearchDataSourceTests.swift

@ -0,0 +1,139 @@
// Copyright © 2015 Abhishek Banthia
import CoreModelKit
import XCTest
@testable import Clocker
class SearchDataSourceTests: XCTestCase {
private var subject: SearchDataSource!
private func setupSubject(searchText: String = "") {
let mockSearchField = NSSearchField()
mockSearchField.stringValue = searchText
subject = SearchDataSource(with: mockSearchField, location: .preferences)
}
private func setupMockData() {
subject.searchTimezones("los")
XCTAssertTrue(subject.calculateChangesets())
let mockTimezone = TimezoneData()
mockTimezone.timezoneID = "PST"
mockTimezone.formattedAddress = "Los Angeles"
subject.setFilteredArrayValue([mockTimezone])
subject.searchTimezones("los")
XCTAssertTrue(subject.calculateChangesets())
}
override func tearDownWithError() throws {
subject = nil
try super.tearDownWithError()
}
func testSearchTimezones() {
setupSubject(searchText: "")
// Test capitalized string
subject.searchTimezones("MUMBAI")
XCTAssert(subject.timezoneFilteredArray.isEmpty == false)
// Test sentence-cased string
subject.searchTimezones("Delhi")
XCTAssert(subject.timezoneFilteredArray.isEmpty == false)
// Test lower-cased string
subject.searchTimezones("california")
XCTAssert(subject.timezoneFilteredArray.isEmpty == false)
}
func testCalculateChangesets() {
setupSubject(searchText: "los")
setupMockData()
subject.cleanupFilterArray()
subject.searchTimezones("los")
XCTAssertTrue(subject.calculateChangesets())
}
func testRetrieveResult() throws {
setupSubject(searchText: "los")
setupMockData()
// 0 will translate to a city search result
let result1 = subject.retrieveResult(0)
let unwrap = try XCTUnwrap(result1)
if let metadata = unwrap as? CoreModelKit.TimezoneData {
XCTAssert(metadata.timezoneID == "PST")
}
// 1 will translate to a timezone search result
let result2 = subject.retrieveResult(1)
let unwrap2 = try XCTUnwrap(result2)
if let metadata = unwrap2 as? TimezoneMetadata {
XCTAssert(metadata.timezone.name == "America/Tijuana")
}
// Test placeForRow
let rowType = subject.placeForRow(0)
XCTAssert(rowType == .city)
let rowType1 = subject.placeForRow(1)
XCTAssert(rowType1 == .timezone)
// Test count
XCTAssertEqual(subject.resultsCount(), 4)
// Test retrieveFilteredResultFromGoogleAPI
let firstResult = try XCTUnwrap(subject.retrieveFilteredResultFromGoogleAPI(0))
XCTAssert(firstResult.timezoneID == "PST")
// filteredArray should only have a count of 1
XCTAssertNil(subject.retrieveFilteredResultFromGoogleAPI(1))
}
func testTableViewDataSourceMethods() {
let mockTableView = NSTableView(frame: CGRect.zero)
setupSubject(searchText: "los")
setupMockData()
let resultsCount = subject.numberOfRows(in: mockTableView)
XCTAssert(resultsCount == 4)
XCTAssert(subject.tableView(mockTableView, heightOfRow: 0) == 30)
}
func testRetrieveSelectedTimezone() {
setupSubject(searchText: "los")
setupMockData()
let result = subject.retrieveSelectedTimezone(0)
XCTAssert(result.timezone.abbreviation == "PDT")
}
func testRetrieveSelectedTimezoneWithEmptySearchField() {
// Setup subject with an empty search field
setupSubject(searchText: CLEmptyString)
subject.searchTimezones("los")
XCTAssertFalse(subject.calculateChangesets())
let mockTimezone = TimezoneData()
mockTimezone.timezoneID = "PST"
mockTimezone.formattedAddress = "Los Angeles"
subject.setFilteredArrayValue([mockTimezone])
subject.searchTimezones("los")
XCTAssertFalse(subject.calculateChangesets())
let result = subject.retrieveSelectedTimezone(1)
XCTAssert(result.timezone.abbreviation == "GMT")
}
func testRetrieveSelectedTimezoneWithEmptySearchFieldWithoutSearchResults() {
// Setup subject with an empty search field
setupSubject(searchText: "los")
setupMockData()
subject.cleanupFilterArray()
let result = subject.retrieveResult(0)
XCTAssertNil(result)
}
}

93
Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift

@ -14,51 +14,60 @@ class StandardMenubarHandlerTests: XCTestCase {
"latitude": "19.0759837", "latitude": "19.0759837",
"longitude": "72.8776559"] "longitude": "72.8776559"]
func testValidStandardMenubarHandler_returnMenubarTitle() { private func makeMockStore(with menubarMode: Int = 1) -> DataStore {
// Wipe all timezones from UserDefaults // Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil) guard let defaults = UserDefaults(suiteName: "com.abhishek.Clocker.StandardMenubarHandlerTests") else {
XCTFail("User defaults couldn't be initialized")
return DataStore.shared()
}
defaults.set(menubarMode, forKey: CLMenubarCompactMode)
defaults.set(0, forKey: CLShowMeetingInMenubar)
XCTAssertNotEqual(defaults, UserDefaults.standard)
return DataStore(with: defaults)
}
private func saveObject(object: TimezoneData,
in store: DataStore,
at index: Int = -1)
{
var defaults = store.timezones()
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: object as Any)
index == -1 ? defaults.append(encodedObject) : defaults.insert(encodedObject, at: index)
store.setTimezones(defaults)
}
func testValidStandardMenubarHandler_returnMenubarTitle() {
let store = makeMockStore()
store.setTimezones(nil)
// Save a menubar selected timezone // Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 1 dataObject.isFavourite = 1
let operationsObject = TimezoneDataOperations(with: dataObject) saveObject(object: dataObject, in: store)
operationsObject.saveObject()
let menubarTimezones = DataStore.shared().menubarTimezones() let menubarTimezones = store.menubarTimezones()
XCTAssertTrue(menubarTimezones?.count == 1) XCTAssertTrue(menubarTimezones?.count == 1, "Count is \(String(describing: menubarTimezones?.count))")
// Set standard menubar in Prefs
UserDefaults.standard.set(1, forKey: CLMenubarCompactMode)
let menubarHandler = MenubarTitleProvider()
let menubarString = menubarHandler.titleForMenubar() ?? ""
// Test menubar string is present
XCTAssertTrue(menubarString.count > 0)
XCTAssertTrue(menubarString.contains("Ghar"))
// Set default back to compact menubar
UserDefaults.standard.set(0, forKey: CLMenubarCompactMode)
} }
func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() { func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() {
let store = makeMockStore()
// Wipe all timezones from UserDefaults // Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil) store.setTimezones(nil)
// Save a menubar selected timezone // Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 0 dataObject.isFavourite = 0
let operationsObject = TimezoneDataOperations(with: dataObject) saveObject(object: dataObject, in: store)
operationsObject.saveObject()
let menubarTimezones = DataStore.shared().menubarTimezones() let menubarTimezones = store.menubarTimezones()
XCTAssertTrue(menubarTimezones?.count == 0) XCTAssertTrue(menubarTimezones?.count == 0)
} }
func testUnfavouritedTimezone_returnNilMenubarString() { func testUnfavouritedTimezone_returnNilMenubarString() {
let store = makeMockStore()
// Wipe all timezones from UserDefaults // Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil) store.setTimezones(nil)
let menubarHandler = MenubarTitleProvider() let menubarHandler = MenubarTitleProvider(with: store)
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)
@ -66,12 +75,42 @@ class StandardMenubarHandlerTests: XCTestCase {
// Save a menubar selected timezone // Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai) let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 0 dataObject.isFavourite = 0
let operationsObject = TimezoneDataOperations(with: dataObject) saveObject(object: dataObject, in: store)
operationsObject.saveObject()
let menubarString = menubarHandler.titleForMenubar() ?? "" let menubarString = menubarHandler.titleForMenubar() ?? ""
// Test menubar string is absent // Test menubar string is absent
XCTAssertTrue(menubarString.count == 0) XCTAssertTrue(menubarString.count == 0)
} }
func testWithEmptyMenubarTimezones() {
let store = makeMockStore()
store.setTimezones(nil)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssertNil(menubarHandler.titleForMenubar())
}
func testWithStandardMenubarMode() {
// Set mode to standard mode
let store = makeMockStore(with: 0)
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 1
saveObject(object: dataObject, in: store)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssertNil(menubarHandler.titleForMenubar())
}
func testProviderPassingAllConditions() {
// Set mode to standard mode
let store = makeMockStore()
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 1
saveObject(object: dataObject, in: store)
let menubarHandler = MenubarTitleProvider(with: store)
XCTAssertNotNil(menubarHandler.titleForMenubar())
}
} }

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"]
) ),
] ]
) )

2
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?

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

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

@ -41,47 +41,4 @@ public extension Date {
} }
self = date self = date
} }
/**
* Init date with components. Hour, minutes, and seconds set to zero.
*
* - parameter year: Year component of new date
* - parameter month: Month component of new date
* - parameter day: Day component of new date
*/
init(year: Int, month: Int, day: Int) {
self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0)
}
/**
* Init date from string, given a format string, according to Apple's date formatting guide, and time zone.
*
* - parameter dateString: Date in the formatting given by the format parameter
* - parameter format: Format style using Apple's date formatting guide
* - parameter timeZone: Time zone of date
*/
init(dateString: String, format: String, timeZone: TimeZone) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .none
dateFormatter.timeZone = timeZone
dateFormatter.dateFormat = format
guard let date = dateFormatter.date(from: dateString) else {
self = Date()
return
}
self = date
}
/**
* Init date from string, given a format string, according to Apple's date formatting guide.
* Time Zone automatically selected as the current time zone.
*
* - parameter dateString: Date in the formatting given by the format parameter
* - parameter format: Format style using Apple's date formatting guide
*/
init(dateString: String, format: String) {
self.init(dateString: dateString, format: format, timeZone: TimeZone.autoupdatingCurrent)
}
} }

667
Clocker/Dependencies/Date Additions/TimePeriod.swift

@ -1,667 +0,0 @@
//
// TimePeriod.swift
// DateTools
//
// Created by Grayson Webster on 8/17/16.
// Copyright © 2016 Grayson Webster. All rights reserved.
//
import Foundation
/**
* In DateTools, time periods are represented by the TimePeriod protocol.
* Required variables and method impleementations are bound below. An inheritable
* implementation of the TimePeriodProtocol is available through the TimePeriodClass
*
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/
public protocol TimePeriodProtocol {
// MARK: - Variables
/**
* The start date for a TimePeriod representing the starting boundary of the time period
*/
var beginning: Date? { get set }
/**
* The end date for a TimePeriod representing the ending boundary of the time period
*/
var end: Date? { get set }
}
public extension TimePeriodProtocol {
// MARK: - Information
/**
* True if the `TimePeriod`'s duration is zero
*/
var isMoment: Bool {
return beginning == end
}
/**
* The duration of the `TimePeriod` in years.
* Returns the max int if beginning or end are nil.
*/
var years: Int {
if beginning != nil, end != nil {
return beginning!.yearsEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in weeks.
* Returns the max int if beginning or end are nil.
*/
var weeks: Int {
if beginning != nil, end != nil {
return beginning!.weeksEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in days.
* Returns the max int if beginning or end are nil.
*/
var days: Int {
if beginning != nil, end != nil {
return beginning!.daysEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in hours.
* Returns the max int if beginning or end are nil.
*/
var hours: Int {
if beginning != nil, end != nil {
return beginning!.hoursEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in minutes.
* Returns the max int if beginning or end are nil.
*/
var minutes: Int {
if beginning != nil, end != nil {
return beginning!.minutesEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in seconds.
* Returns the max int if beginning or end are nil.
*/
var seconds: Int {
if beginning != nil, end != nil {
return beginning!.secondsEarlier(than: end!)
}
return Int.max
}
/**
* The duration of the `TimePeriod` in a time chunk.
* Returns a time chunk with all zeroes if beginning or end are nil.
*/
var chunk: TimeChunk {
if beginning != nil, end != nil {
return beginning!.chunkBetween(date: end!)
}
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
}
/**
* The length of time between the beginning and end dates of the
* `TimePeriod` as a `TimeInterval`.
*/
var duration: TimeInterval {
if beginning != nil, end != nil {
return abs(beginning!.timeIntervalSince(end!))
}
return TimeInterval(Double.greatestFiniteMagnitude)
}
// MARK: - Time Period Relationships
/**
* The relationship of the self `TimePeriod` to the given `TimePeriod`.
* Relations are stored in Enums.swift. Formal defnitions available in the provided
* links:
* [GitHub](https://github.com/MatthewYork/DateTools#relationships),
* [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET)
*
* - parameter period: The time period to compare to self
*
* - returns: The relationship between self and the given time period
*/
func relation(to period: TimePeriodProtocol) -> Relation {
// Make sure that all start and end points exist for comparison
if beginning != nil, end != nil, period.beginning != nil, period.end != nil {
// Make sure time periods are of positive durations
if beginning!.isEarlier(than: end!), period.beginning!.isEarlier(than: period.end!) {
// Make comparisons
if period.end!.isEarlier(than: beginning!) {
return .after
} else if period.end!.equals(beginning!) {
return .startTouching
} else if period.beginning!.isEarlier(than: beginning!), period.end!.isEarlier(than: end!) {
return .startInside
} else if period.beginning!.equals(beginning!), period.end!.isLater(than: end!) {
return .insideStartTouching
} else if period.beginning!.equals(beginning!), period.end!.isEarlier(than: end!) {
return .enclosingStartTouching
} else if period.beginning!.isLater(than: beginning!), period.end!.isEarlier(than: end!) {
return .enclosing
} else if period.beginning!.isLater(than: beginning!), period.end!.equals(end!) {
return .enclosingEndTouching
} else if period.beginning!.equals(beginning!), period.end!.equals(end!) {
return .exactMatch
} else if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: end!) {
return .inside
} else if period.beginning!.isEarlier(than: beginning!), period.end!.equals(end!) {
return .insideEndTouching
} else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) {
return .endInside
} else if period.beginning!.equals(end!), period.end!.isLater(than: end!) {
return .endTouching
} else if period.beginning!.isLater(than: end!) {
return .before
}
}
}
return .none
}
/**
* If `self.beginning` and `self.end` are equal to the beginning and end of the
* given `TimePeriod`.
*
* - parameter period: The time period to compare to self
*
* - returns: True if the periods are the same
*/
func equals(_ period: TimePeriodProtocol) -> Bool {
return beginning == period.beginning && end == period.end
}
/**
* If the given `TimePeriod`'s beginning is before `self.beginning` and
* if the given 'TimePeriod`'s end is after `self.end`.
*
* - parameter period: The time period to compare to self
*
* - returns: True if self is inside of the given `TimePeriod`
*/
func isInside(of period: TimePeriodProtocol) -> Bool {
return period.beginning!.isEarlierThanOrEqual(to: beginning!) && period.end!.isLaterThanOrEqual(to: end!)
}
/**
* If the given Date is after `self.beginning` and before `self.end`.
*
* - parameter period: The time period to compare to self
* - parameter interval: Whether the edge of the date is included in the calculation
*
* - returns: True if the given `TimePeriod` is inside of self
*/
func contains(_ date: Date, interval: Interval) -> Bool {
if interval == .open {
return beginning!.isEarlier(than: date) && end!.isLater(than: date)
} else if interval == .closed {
return (beginning!.isEarlierThanOrEqual(to: date) && end!.isLaterThanOrEqual(to: date))
}
return false
}
/**
* If the given `TimePeriod`'s beginning is after `self.beginning` and
* if the given 'TimePeriod`'s after is after `self.end`.
*
* - parameter period: The time period to compare to self
*
* - returns: True if the given `TimePeriod` is inside of self
*/
func contains(_ period: TimePeriodProtocol) -> Bool {
return beginning!.isEarlierThanOrEqual(to: period.beginning!) && end!.isLaterThanOrEqual(to: period.end!)
}
/**
* If self and the given `TimePeriod` share any sub-`TimePeriod`.
*
* - parameter period: The time period to compare to self
*
* - returns: True if there is a period of time that is shared by both `TimePeriod`s
*/
func overlaps(with period: TimePeriodProtocol) -> Bool {
// Outside -> Inside
if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: beginning!) {
return true
}
// Enclosing
else if period.beginning!.isLaterThanOrEqual(to: beginning!), period.end!.isEarlierThanOrEqual(to: end!) {
return true
}
// Inside -> Out
else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) {
return true
}
return false
}
/**
* If self and the given `TimePeriod` overlap or the period's edges touch.
*
* - parameter period: The time period to compare to self
*
* - returns: True if there is a period of time or moment that is shared by both `TimePeriod`s
*/
func intersects(with period: TimePeriodProtocol) -> Bool {
return relation(to: period) != .after && relation(to: period) != .before
}
/**
* If self and the given `TimePeriod` have no overlap or touching edges.
*
* - parameter period: The time period to compare to self
*
* - returns: True if there is a period of time between self and the given `TimePeriod` not contained by either period
*/
func hasGap(between period: TimePeriodProtocol) -> Bool {
return isBefore(period: period) || isAfter(period: period)
}
/**
* The period of time between self and the given `TimePeriod` not contained by either.
*
* - parameter period: The time period to compare to self
*
* - returns: The gap between the periods. Zero if there is no gap.
*/
func gap(between period: TimePeriodProtocol) -> TimeInterval {
if end!.isEarlier(than: period.beginning!) {
return abs(end!.timeIntervalSince(period.beginning!))
} else if period.end!.isEarlier(than: beginning!) {
return abs(period.end!.timeIntervalSince(beginning!))
}
return 0
}
/**
* The period of time between self and the given `TimePeriod` not contained by either
* as a `TimeChunk`.
*
* - parameter period: The time period to compare to self
*
* - returns: The gap between the periods, zero if there is no gap
*/
func gap(between period: TimePeriodProtocol) -> TimeChunk? {
if end != nil, period.beginning != nil {
return (end?.chunkBetween(date: period.beginning!))!
}
return nil
}
/**
* If self is after the given `TimePeriod` chronologically. (A gap must exist between the two).
*
* - parameter period: The time period to compare to self
*
* - returns: True if self is after the given `TimePeriod`
*/
func isAfter(period: TimePeriodProtocol) -> Bool {
return relation(to: period) == .after
}
/**
* If self is before the given `TimePeriod` chronologically. (A gap must exist between the two).
*
* - parameter period: The time period to compare to self
*
* - returns: True if self is after the given `TimePeriod`
*/
func isBefore(period: TimePeriodProtocol) -> Bool {
return relation(to: period) == .before
}
// MARK: - Shifts
// MARK: In Place
/**
* In place, shift the `TimePeriod` by a `TimeInterval`
*
* - parameter timeInterval: The time interval to shift the period by
*/
mutating func shift(by timeInterval: TimeInterval) {
beginning?.addTimeInterval(timeInterval)
end?.addTimeInterval(timeInterval)
}
/**
* In place, shift the `TimePeriod` by a `TimeChunk`
*
* - parameter chunk: The time chunk to shift the period by
*/
mutating func shift(by chunk: TimeChunk) {
beginning = beginning?.add(chunk)
end = end?.add(chunk)
}
// MARK: - Lengthen / Shorten
// MARK: In Place
/**
* In place, lengthen the `TimePeriod`, anchored at the beginning, end or center
*
* - parameter timeInterval: The time interval to lengthen the period by
* - parameter anchor: The anchor point from which to make the change
*/
mutating func lengthen(by timeInterval: TimeInterval, at anchor: Anchor) {
switch anchor {
case .beginning:
end = end?.addingTimeInterval(timeInterval)
case .center:
beginning = beginning?.addingTimeInterval(-timeInterval / 2.0)
end = end?.addingTimeInterval(timeInterval / 2.0)
case .end:
beginning = beginning?.addingTimeInterval(-timeInterval)
}
}
/**
* In place, lengthen the `TimePeriod`, anchored at the beginning or end
*
* - parameter chunk: The time chunk to lengthen the period by
* - parameter anchor: The anchor point from which to make the change
*/
mutating func lengthen(by chunk: TimeChunk, at anchor: Anchor) {
switch anchor {
case .beginning:
end = end?.add(chunk)
case .center:
// Do not lengthen by TimeChunk at center
Swift.print("Mutation via chunk from center anchor is not supported.")
case .end:
beginning = beginning?.subtract(chunk)
}
}
/**
* In place, shorten the `TimePeriod`, anchored at the beginning, end or center
*
* - parameter timeInterval: The time interval to shorten the period by
* - parameter anchor: The anchor point from which to make the change
*/
mutating func shorten(by timeInterval: TimeInterval, at anchor: Anchor) {
switch anchor {
case .beginning:
end = end?.addingTimeInterval(-timeInterval)
case .center:
beginning = beginning?.addingTimeInterval(timeInterval / 2.0)
end = end?.addingTimeInterval(-timeInterval / 2.0)
case .end:
beginning = beginning?.addingTimeInterval(timeInterval)
}
}
/**
* In place, shorten the `TimePeriod`, anchored at the beginning or end
*
* - parameter chunk: The time chunk to shorten the period by
* - parameter anchor: The anchor point from which to make the change
*/
mutating func shorten(by chunk: TimeChunk, at anchor: Anchor) {
switch anchor {
case .beginning:
end = end?.subtract(chunk)
case .center:
// Do not shorten by TimeChunk at center
Swift.print("Mutation via chunk from center anchor is not supported.")
case .end:
beginning = beginning?.add(chunk)
}
}
}
/**
* In DateTools, time periods are represented by the case TimePeriod class
* and come with a suite of initializaiton, manipulation, and comparison methods
* to make working with them a breeze.
*
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information.
*/
open class TimePeriod: TimePeriodProtocol {
// MARK: - Variables
/**
* The start date for a TimePeriod representing the starting boundary of the time period
*/
public var beginning: Date?
/**
* The end date for a TimePeriod representing the ending boundary of the time period
*/
public var end: Date?
// MARK: - Initializers
init() {}
init(beginning: Date?, end: Date?) {
self.beginning = beginning
self.end = end
}
init(beginning: Date, duration: TimeInterval) {
self.beginning = beginning
end = beginning + duration
}
init(end: Date, duration: TimeInterval) {
self.end = end
beginning = end.addingTimeInterval(-duration)
}
init(beginning: Date, chunk: TimeChunk) {
self.beginning = beginning
end = beginning + chunk
}
init(end: Date, chunk: TimeChunk) {
self.end = end
beginning = end - chunk
}
init(chunk: TimeChunk) {
beginning = Date()
end = beginning?.add(chunk)
}
// MARK: - Shifted
/**
* Shift the `TimePeriod` by a `TimeInterval`
*
* - parameter timeInterval: The time interval to shift the period by
*
* - returns: The new, shifted `TimePeriod`
*/
func shifted(by timeInterval: TimeInterval) -> TimePeriod {
let timePeriod = TimePeriod()
timePeriod.beginning = beginning?.addingTimeInterval(timeInterval)
timePeriod.end = end?.addingTimeInterval(timeInterval)
return timePeriod
}
/**
* Shift the `TimePeriod` by a `TimeChunk`
*
* - parameter chunk: The time chunk to shift the period by
*
* - returns: The new, shifted `TimePeriod`
*/
func shifted(by chunk: TimeChunk) -> TimePeriod {
let timePeriod = TimePeriod()
timePeriod.beginning = beginning?.add(chunk)
timePeriod.end = end?.add(chunk)
return timePeriod
}
// MARK: - Lengthen / Shorten
// MARK: New
/**
* Lengthen the `TimePeriod` by a `TimeInterval`
*
* - parameter timeInterval: The time interval to lengthen the period by
* - parameter anchor: The anchor point from which to make the change
*
* - returns: The new, lengthened `TimePeriod`
*/
func lengthened(by timeInterval: TimeInterval, at anchor: Anchor) -> TimePeriod {
let timePeriod = TimePeriod()
switch anchor {
case .beginning:
timePeriod.beginning = beginning
timePeriod.end = end?.addingTimeInterval(timeInterval)
case .center:
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval)
timePeriod.end = end?.addingTimeInterval(timeInterval)
case .end:
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval)
timePeriod.end = end
}
return timePeriod
}
/**
* Lengthen the `TimePeriod` by a `TimeChunk`
*
* - parameter chunk: The time chunk to lengthen the period by
* - parameter anchor: The anchor point from which to make the change
*
* - returns: The new, lengthened `TimePeriod`
*/
func lengthened(by chunk: TimeChunk, at anchor: Anchor) -> TimePeriod {
let timePeriod = TimePeriod()
switch anchor {
case .beginning:
timePeriod.beginning = beginning
timePeriod.end = end?.add(chunk)
case .center:
Swift.print("Mutation via chunk from center anchor is not supported.")
case .end:
timePeriod.beginning = beginning?.add(-chunk)
timePeriod.end = end
}
return timePeriod
}
/**
* Shorten the `TimePeriod` by a `TimeInterval`
*
* - parameter timeInterval: The time interval to shorten the period by
* - parameter anchor: The anchor point from which to make the change
*
* - returns: The new, shortened `TimePeriod`
*/
func shortened(by timeInterval: TimeInterval, at anchor: Anchor) -> TimePeriod {
let timePeriod = TimePeriod()
switch anchor {
case .beginning:
timePeriod.beginning = beginning
timePeriod.end = end?.addingTimeInterval(-timeInterval)
case .center:
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval / 2)
timePeriod.end = end?.addingTimeInterval(timeInterval / 2)
case .end:
timePeriod.beginning = beginning?.addingTimeInterval(timeInterval)
timePeriod.end = end
}
return timePeriod
}
/**
* Shorten the `TimePeriod` by a `TimeChunk`
*
* - parameter chunk: The time chunk to shorten the period by
* - parameter anchor: The anchor point from which to make the change
*
* - returns: The new, shortened `TimePeriod`
*/
func shortened(by chunk: TimeChunk, at anchor: Anchor) -> TimePeriod {
let timePeriod = TimePeriod()
switch anchor {
case .beginning:
timePeriod.beginning = beginning
timePeriod.end = end?.subtract(chunk)
case .center:
Swift.print("Mutation via chunk from center anchor is not supported.")
case .end:
timePeriod.beginning = beginning?.add(-chunk)
timePeriod.end = end
}
return timePeriod
}
// MARK: - Operator Overloads
/**
* Operator overload for checking if two `TimePeriod`s are equal
*/
static func == (leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool {
return leftAddend.equals(rightAddend)
}
// Default anchor = beginning
/**
* Operator overload for lengthening a `TimePeriod` by a `TimeInterval`
*/
static func + (leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning)
}
/**
* Operator overload for lengthening a `TimePeriod` by a `TimeChunk`
*/
static func + (leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod {
return leftAddend.lengthened(by: rightAddend, at: .beginning)
}
// Default anchor = beginning
/**
* Operator overload for shortening a `TimePeriod` by a `TimeInterval`
*/
static func - (minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning)
}
/**
* Operator overload for shortening a `TimePeriod` by a `TimeChunk`
*/
static func - (minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod {
return minuend.shortened(by: subtrahend, at: .beginning)
}
/**
* Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol`
*/
static func == (left: TimePeriod, right: TimePeriodProtocol) -> Bool {
return left.equals(right)
}
}

177
Clocker/Dependencies/Date Additions/TimePeriodChain.swift

@ -1,177 +0,0 @@
//
// TimePeriodChain.swift
// DateTools
//
// Created by Grayson Webster on 8/17/16.
// Copyright © 2016 Grayson Webster. All rights reserved.
//
import Foundation
/**
* Time period chains serve as a tightly coupled set of time periods. They are
* always organized by start and end date, and have their own characteristics like
* a StartDate and EndDate that are extrapolated from the time periods within. Time
* period chains do not allow overlaps within their set of time periods. This type of
* group is ideal for modeling schedules like sequential meetings or appointments.
*
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information.
*/
open class TimePeriodChain: TimePeriodGroup {
// MARK: - Chain Existence Manipulation
/**
* Append a TimePeriodProtocol to the periods array and update the Chain's
* beginning and end.
*
* - parameter period: TimePeriodProtocol to add to the collection
*/
public func append(_ period: TimePeriodProtocol) {
let beginning = (periods.isEmpty == false) ? periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
periods.append(newPeriod)
// Update updateExtremes
if periods.count == 1 {
_beginning = period.beginning
_end = period.end
} else {
_end = _end?.addingTimeInterval(period.duration)
}
}
/**
* Append a TimePeriodProtocol array to the periods array and update the Chain's
* beginning and end.
*
* - parameter periodArray: TimePeriodProtocol list to add to the collection
*/
public func append<G: TimePeriodGroup>(contentsOf group: G) {
for period in group.periods {
let beginning = (periods.isEmpty == false) ? periods.last!.end! : period.beginning
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration)
periods.append(newPeriod)
// Update updateExtremes
if periods.count == 1 {
_beginning = period.beginning
_end = period.end
} else {
_end = _end?.addingTimeInterval(period.duration)
}
}
}
/**
* Insert period into periods array at given index.
*
* - parameter newElement: The period to insert
* - parameter index: Index to insert period at
*/
public func insert(_ period: TimePeriodProtocol, at index: Int) {
// Check for special zero case which takes the beginning date
if index == 0, period.beginning != nil, period.end != nil {
// Insert new period
periods.insert(period, at: index)
} else if period.beginning != nil, period.end != nil {
// Insert new period
periods.insert(period, at: index)
} else {
Swift.print("All TimePeriods in a TimePeriodChain must contain a defined start and end date")
return
}
// Shift all periods after inserted period
for i in 0 ..< periods.count {
if i > index, i > 0 {
let currentPeriod = TimePeriod(beginning: period.beginning, end: period.end)
periods[i].beginning = periods[i - 1].end
periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration)
}
}
updateExtremes()
}
/**
* Remove from period array at the given index.
*
* - parameter at: The index in the collection to remove
*/
public func remove(at index: Int) {
// Retrieve duration of period to be removed
let duration = periods[index].duration
// Remove period
periods.remove(at: index)
// Shift all periods after inserted period
for i in index ..< periods.count {
periods[i].shift(by: -duration)
}
updateExtremes()
}
/**
* Remove all periods from period array.
*/
public func removeAll() {
periods.removeAll()
updateExtremes()
}
// MARK: - Chain Content Manipulation
/**
* In place, shifts all chain time periods by a given time interval
*
* - parameter duration: The time interval to shift the period by
*/
public func shift(by duration: TimeInterval) {
for var period in periods {
period.shift(by: duration)
}
_beginning = _beginning?.addingTimeInterval(duration)
_end = _end?.addingTimeInterval(duration)
}
override public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform)
}
override public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded)
}
override internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult)
}
/**
* Removes the last object from the `TimePeriodChain` and returns it
*
*/
public func pop() -> TimePeriodProtocol? {
let period = periods.popLast()
updateExtremes()
return period
}
internal func updateExtremes() {
_beginning = periods.first?.beginning
_end = periods.last?.end
}
// MARK: - Operator Overloads
/**
* Operator overload for comparing `TimePeriodChain`s to each other
*/
public static func == (left: TimePeriodChain, right: TimePeriodChain) -> Bool {
return left.equals(right)
}
}

267
Clocker/Dependencies/Date Additions/TimePeriodCollection.swift

@ -1,267 +0,0 @@
//
// TimePeriodCollection.swift
// DateTools
//
// Created by Grayson Webster on 8/17/16.
// Copyright © 2016 Grayson Webster. All rights reserved.
//
import Foundation
/**
* Time period collections serve as loose sets of time periods. They are
* unorganized unless you decide to sort them, and have their own characteristics
* like a `beginning` and `end` that are extrapolated from the time periods within. Time
* period collections allow overlaps within their set of time periods.
*
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information.
*/
open class TimePeriodCollection: TimePeriodGroup {
// MARK: - Collection Manipulation
/**
* Append a TimePeriodProtocol to the periods array and check if the Collection's
* beginning and end should change.
*
* - parameter period: TimePeriodProtocol to add to the collection
*/
public func append(_ period: TimePeriodProtocol) {
periods.append(period)
updateExtremes(period: period)
}
/**
* Append a TimePeriodProtocol array to the periods array and check if the Collection's
* beginning and end should change.
*
* - parameter periodArray: TimePeriodProtocol list to add to the collection
*/
public func append(_ periodArray: [TimePeriodProtocol]) {
for period in periodArray {
periods.append(period)
updateExtremes(period: period)
}
}
/**
* Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's
* beginning and end should change.
*
* - parameter newPeriods: TimePeriodGroup to merge periods arrays with
*/
public func append<C: TimePeriodGroup>(contentsOf newPeriods: C) {
for period in newPeriods as TimePeriodGroup {
periods.append(period)
updateExtremes(period: period)
}
}
/**
* Insert period into periods array at given index.
*
* - parameter newElement: The period to insert
* - parameter index: Index to insert period at
*/
public func insert(_ newElement: TimePeriodProtocol, at index: Int) {
periods.insert(newElement, at: index)
updateExtremes(period: newElement)
}
/**
* Remove from period array at the given index.
*
* - parameter at: The index in the collection to remove
*/
public func remove(at: Int) {
periods.remove(at: at)
updateExtremes()
}
/**
* Remove all periods from period array.
*/
public func removeAll() {
periods.removeAll()
updateExtremes()
}
// MARK: - Sorting
// In place
/**
* Sort periods array in place by beginning
*/
public func sortByBeginning() {
sort { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
} else if period1.beginning == nil {
return true
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
}
/**
* Sort periods array in place
*/
public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
periods.sort(by: areInIncreasingOrder)
}
// New collection
/**
* Return collection with sorted periods array by beginning
*
* - returns: Collection with sorted periods
*/
public func sortedByBeginning() -> TimePeriodCollection {
let array = periods.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
} else if period1.beginning == nil {
return true
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
let collection = TimePeriodCollection()
collection.append(array)
return collection
}
/**
* Return collection with sorted periods array
*
* - returns: Collection with sorted periods
*/
public func sorted(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) -> TimePeriodCollection {
let collection = TimePeriodCollection()
collection.append(periods.sorted(by: areInIncreasingOrder))
return collection
}
// MARK: - Collection Relationship
// Potentially use .reduce() instead of these functions
/**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
* whose start and end dates fall completely inside the interval of the given `TimePeriod`.
*
* - parameter period: The period to compare each other period against
*
* - returns: Collection of periods inside the given period
*/
public func allInside(in period: TimePeriodProtocol) -> TimePeriodCollection {
let collection = TimePeriodCollection()
// Filter by period
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.isInside(of: period)
}
return collection
}
/**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing
* the given date.
*
* - parameter date: The date to compare each period to
*
* - returns: Collection of periods intersected by the given date
*/
public func periodsIntersected(by date: Date) -> TimePeriodCollection {
let collection = TimePeriodCollection()
// Filter by period
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.contains(date, interval: .closed)
}
return collection
}
/**
* Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s
* containing either the start date or the end date--or both--of the given `TimePeriod`.
*
* - parameter period: The period to compare each other period to
*
* - returns: Collection of periods intersected by the given period
*/
public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection {
let collection = TimePeriodCollection()
// Filter by periop
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.intersects(with: period)
}
return collection
}
// MARK: - Map
public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection {
var mappedArray = [TimePeriodProtocol]()
mappedArray = try periods.map(transform)
let mappedCollection = TimePeriodCollection()
for period in mappedArray {
mappedCollection.periods.append(period)
mappedCollection.updateExtremes(period: period)
}
return mappedCollection
}
// MARK: - Operator Overloads
/**
* Operator overload for comparing `TimePeriodCollection`s to each other
*/
public static func == (left: TimePeriodCollection, right: TimePeriodCollection) -> Bool {
return left.equals(right)
}
// MARK: - Helpers
internal func updateExtremes(period: TimePeriodProtocol) {
// Check incoming period against previous beginning and end date
if count == 1 {
_beginning = period.beginning
_end = period.end
} else {
_beginning = nilOrEarlier(date1: _beginning, date2: period.beginning)
_end = nilOrLater(date1: _end, date2: period.end)
}
}
internal func updateExtremes() {
if periods.isEmpty {
_beginning = nil
_end = nil
} else {
_beginning = periods[0].beginning
_end = periods[0].end
for i in 1 ..< periods.count {
_beginning = nilOrEarlier(date1: _beginning, date2: periods[i].beginning)
_end = nilOrEarlier(date1: _end, date2: periods[i].end)
}
}
}
internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil {
return nil
} else {
return date1!.earlierDate(date2!)
}
}
internal func nilOrLater(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil {
return nil
} else {
return date1!.laterDate(date2!)
}
}
}

146
Clocker/Dependencies/Date Additions/TimePeriodGroup.swift

@ -1,146 +0,0 @@
//
// TimePeriodGroup.swift
// DateTools
//
// Created by Grayson Webster on 8/17/16.
// Copyright © 2016 Grayson Webster. All rights reserved.
//
import Foundation
/**
* Time period groups are the final abstraction of date and time in DateTools. Here, time
* periods are gathered and organized into something useful. There are two main types of time
* period groups, `TimePeriodCollection` and `TimePeriodChain`.
*
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information.
*/
open class TimePeriodGroup: Sequence {
// MARK: - Variables
/**
* The array of periods that define the group.
*/
internal var periods: [TimePeriodProtocol] = []
internal var _beginning: Date?
internal var _end: Date?
/**
* The earliest beginning date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil beginning date (indefinite).
* (Read Only)
*/
public var beginning: Date? {
return _beginning
}
/**
* The latest end date of a `TimePeriod` in the group.
* Nil if any `TimePeriod` in group has a nil end date (indefinite).
* (Read Only)
*/
public var end: Date? {
return _end
}
/**
* The number of periods in the periods array.
*/
public var count: Int {
return periods.count
}
/**
* The total amount of time between the earliest and latest dates stored in the
* periods array. Nil if any beginning or end date in any contained period is nil.
*/
public var duration: TimeInterval? {
if beginning != nil, end != nil {
return end!.timeIntervalSince(beginning!)
}
return nil
}
// MARK: - Initializers
public init() {}
// MARK: - Comparisons
/**
* If `self.periods` contains the exact elements as the given group's periods array.
*
* - parameter group: The group to compare to self
*
* - returns: True if the periods arrays are the same
*/
public func equals(_ group: TimePeriodGroup) -> Bool {
return containSameElements(array1: periods, group.periods)
}
// MARK: - Sequence Protocol
public func makeIterator() -> IndexingIterator<[TimePeriodProtocol]> {
return periods.makeIterator()
}
public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform)
}
public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded)
}
public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows {
return try periods.forEach(body)
}
public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence<TimePeriodProtocol>] {
return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init)
}
subscript(index: Int) -> TimePeriodProtocol {
return periods[index]
}
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult)
}
internal func containSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool {
guard array1.count == array2.count else {
return false // No need to sorting if they already have different counts
}
let compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
} else if period1.beginning == nil {
return true
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
let compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
} else if period1.beginning == nil {
return true
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
for x in 0 ..< compArray1.count {
if !compArray1[x].equals(compArray2[x]) {
return false
}
}
return true
}
}

11
Clocker/Events and Reminders/CalendarHandler.swift

@ -391,7 +391,8 @@ extension EventCenter {
// Check for Zoom links // Check for Zoom links
if actualLink.contains("zoom.us/j/") if actualLink.contains("zoom.us/j/")
|| actualLink.contains("zoom.us/s/") || actualLink.contains("zoom.us/s/")
|| actualLink.contains("zoom.us/w/") { || actualLink.contains("zoom.us/w/")
{
// Create a Zoom App link // Create a Zoom App link
let workspace = NSWorkspace.shared let workspace = NSWorkspace.shared
if workspace.urlForApplication(toOpen: URL(string: "zoommtg://")!) != nil { if workspace.urlForApplication(toOpen: URL(string: "zoommtg://")!) != nil {
@ -417,7 +418,8 @@ extension EventCenter {
|| actualLink.contains("public.senfcall.de") || actualLink.contains("public.senfcall.de")
|| actualLink.contains("youcanbook.me/zoom/") || actualLink.contains("youcanbook.me/zoom/")
|| actualLink.contains("workplace.com/groupcall") || actualLink.contains("workplace.com/groupcall")
|| actualLink.contains("bluejeans.com/") { || actualLink.contains("bluejeans.com/")
{
if let meetingLink = result.url { if let meetingLink = result.url {
return meetingLink return meetingLink
} }
@ -494,8 +496,9 @@ struct EventInfo {
let timeSince = Date().timeAgo(since: event.startDate) let timeSince = Date().timeAgo(since: event.startDate)
let withoutAn = timeSince.replacingOccurrences(of: "an", with: CLEmptyString) let withoutAn = timeSince.replacingOccurrences(of: "an", with: CLEmptyString)
let withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: CLEmptyString) let withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: CLEmptyString)
// If the user has not turned on seconds granularity for one of the timezones,
return "in \(withoutAgo.lowercased())" // we return "in 12 seconds" which looks weird.
return withoutAgo.contains("seconds") ? "starts soon" : "in \(withoutAgo.lowercased())"
} else if event.startDate.isTomorrow { } else if event.startDate.isTomorrow {
let hoursUntil = event.startDate.hoursUntil let hoursUntil = event.startDate.hoursUntil
return "in \(hoursUntil)h" return "in \(hoursUntil)h"

3
Clocker/Events and Reminders/RemindersHandler.swift

@ -51,7 +51,8 @@ extension EventCenter {
timezone: String, timezone: String,
alertIndex: Int, alertIndex: Int,
reminderDate: Date, reminderDate: Date,
additionalNotes: String?) -> Bool { additionalNotes: String?) -> Bool
{
initializeStoreIfNeccesary() initializeStoreIfNeccesary()
if reminderAccessNotDetermined() || reminderAccessDenied() { if reminderAccessNotDetermined() || reminderAccessDenied() {

10
Clocker/Menu Bar/StatusContainerView.swift

@ -31,10 +31,12 @@ func compactWidth(for timezone: TimezoneData) -> Int {
if timeFormat == DateFormat.twelveHour if timeFormat == DateFormat.twelveHour
|| timeFormat == DateFormat.twelveHourWithSeconds || timeFormat == DateFormat.twelveHourWithSeconds
|| timeFormat == DateFormat.twelveHourWithZero || timeFormat == DateFormat.twelveHourWithZero
|| timeFormat == DateFormat.twelveHourWithSeconds { || timeFormat == DateFormat.twelveHourWithSeconds
{
totalWidth += 20 totalWidth += 20
} else if timeFormat == DateFormat.twentyFourHour } else if timeFormat == DateFormat.twentyFourHour
|| timeFormat == DateFormat.twentyFourHourWithSeconds { || timeFormat == DateFormat.twentyFourHourWithSeconds
{
totalWidth += 0 totalWidth += 0
} }
@ -75,7 +77,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 +136,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)

3
Clocker/Menu Bar/StatusItemHandler.swift

@ -112,7 +112,8 @@ class StatusItemHandler: NSObject {
userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification, userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification,
object: self, object: self,
queue: mainQueue) { _ in queue: mainQueue)
{ _ in
self.setupStatusItem() self.setupStatusItem()
} }
} }

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),
]) ])
} }

7
Clocker/Onboarding/OnboardingSearchController.swift

@ -135,7 +135,8 @@ class OnboardingSearchController: NSViewController {
private func setupLabelHidingTimer() { private func setupLabelHidingTimer() {
Timer.scheduledTimer(withTimeInterval: 5, Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in repeats: false)
{ _ in
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
self.setInfoLabel(CLEmptyString) self.setInfoLabel(CLEmptyString)
} }
@ -227,7 +228,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))
@ -413,7 +414,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)

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
} }

43
Clocker/Overall App/DataStore.swift

@ -18,12 +18,13 @@ enum ViewType {
case dayInMenubar case dayInMenubar
case menubarCompactMode case menubarCompactMode
case dstTransitionInfo case dstTransitionInfo
case sync
} }
class DataStore: NSObject { class DataStore: NSObject {
private static var sharedStore = DataStore(with: UserDefaults.standard) private static var sharedStore = DataStore(with: UserDefaults.standard)
private var userDefaults: UserDefaults! private var userDefaults: UserDefaults!
private var ubiquitousStore: NSUbiquitousKeyValueStore! private var ubiquitousStore: NSUbiquitousKeyValueStore?
// Since these pref can accessed every second, let's cache this // Since these pref can accessed every second, let's cache this
private var shouldDisplayDayInMenubar: Bool = false private var shouldDisplayDayInMenubar: Bool = false
@ -41,9 +42,39 @@ class DataStore: NSObject {
init(with defaults: UserDefaults) { init(with defaults: UserDefaults) {
super.init() super.init()
userDefaults = defaults userDefaults = defaults
ubiquitousStore = NSUbiquitousKeyValueStore.default
shouldDisplayDayInMenubar = shouldDisplay(.dayInMenubar) shouldDisplayDayInMenubar = shouldDisplay(.dayInMenubar)
shouldDisplayDateInMenubar = shouldDisplay(.dateInMenubar) shouldDisplayDateInMenubar = shouldDisplay(.dateInMenubar)
setupSyncNotification()
}
func setupSyncNotification() {
if shouldDisplay(.sync) {
ubiquitousStore = NSUbiquitousKeyValueStore.default
NotificationCenter.default.addObserver(self,
selector: #selector(ubiquitousKeyValueStoreChanged),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: NSUbiquitousKeyValueStore.default)
ubiquitousStore?.synchronize()
} else {
NotificationCenter.default.removeObserver(self,
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: nil)
}
}
@objc func ubiquitousKeyValueStoreChanged(_ notification: Notification) {
let userInfo = notification.userInfo ?? [:]
let ubiquitousStore = notification.object as? NSUbiquitousKeyValueStore
print("--- User Info is \(userInfo)")
let currentTimezones = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data]
let cloudTimezones = ubiquitousStore?.object(forKey: CLDefaultPreferenceKey) as? [Data]
if cloudTimezones != currentTimezones {
Logger.info("Syncing local timezones with data from the ☁")
userDefaults.set(cloudTimezones, forKey: CLDefaultPreferenceKey)
NotificationCenter.default.post(name: DataStore.didSyncFromExternalSourceNotification,
object: self)
}
} }
func timezones() -> [Data] { func timezones() -> [Data] {
@ -57,7 +88,7 @@ class DataStore: NSObject {
func setTimezones(_ timezones: [Data]?) { func setTimezones(_ timezones: [Data]?) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey) userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
// iCloud sync // iCloud sync
ubiquitousStore.set(timezones, forKey: CLDefaultPreferenceKey) ubiquitousStore?.set(timezones, forKey: CLDefaultPreferenceKey)
} }
func menubarTimezones() -> [Data]? { func menubarTimezones() -> [Data]? {
@ -158,6 +189,8 @@ class DataStore: NSObject {
} }
return value == 0 return value == 0
case .sync:
return shouldDisplayHelper(CLEnableSyncKey)
} }
} }
@ -170,3 +203,7 @@ class DataStore: NSObject {
return value.isEqual(to: NSNumber(value: 0)) return value.isEqual(to: NSNumber(value: 0))
} }
} }
extension DataStore {
public static let didSyncFromExternalSourceNotification: NSNotification.Name = .init("didSyncFromExternalSourceNotification")
}

1
Clocker/Overall App/DateFormatterManager.swift

@ -3,7 +3,6 @@
import Cocoa import Cocoa
class DateFormatterManager: NSObject { class DateFormatterManager: NSObject {
private static var dateFormatter = DateFormatter() private static var dateFormatter = DateFormatter()
private static var calendarDateFormatter = DateFormatter() private static var calendarDateFormatter = DateFormatter()
private static var simpleFormatter = DateFormatter() private static var simpleFormatter = DateFormatter()

1
Clocker/Overall App/Strings.swift

@ -37,3 +37,4 @@ let CLInstallHomeIndicatorObject = "installHomeIndicatorObject"
let CLSwitchToCompactModeAlert = "com.abhishek.switchToCompactMode" let CLSwitchToCompactModeAlert = "com.abhishek.switchToCompactMode"
let CLDisplayDSTTransitionInfo = "com.abhishek.showDSTTransitionInfo" let CLDisplayDSTTransitionInfo = "com.abhishek.showDSTTransitionInfo"
let CLAppleInterfaceStyleKey = "AppleInterfaceStyle" let CLAppleInterfaceStyleKey = "AppleInterfaceStyle"
let CLEnableSyncKey = "com.abhishek.enableSync"

3
Clocker/Overall App/Themer.swift

@ -459,7 +459,8 @@ extension Themer {
_ darkImage: NSImage, _ darkImage: NSImage,
_ systemImage: NSImage, _ systemImage: NSImage,
_ solarizedLightImage: NSImage, _ solarizedLightImage: NSImage,
_ solarizedDarkImage: NSImage) -> NSImage { _ solarizedDarkImage: NSImage) -> NSImage
{
if #available(macOS 10.14, *) { if #available(macOS 10.14, *) {
switch themeIndex { switch themeIndex {
case .light: case .light:

2
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -283,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

10
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()
@ -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,
@ -322,7 +322,8 @@ class NotesPopover: NSViewController {
} }
private func updateTimezoneInDefaultPreferences(with override: Int, private func updateTimezoneInDefaultPreferences(with override: Int,
_: OverrideType) { _: OverrideType)
{
let timezones = DataStore.shared().timezones() let timezones = DataStore.shared().timezones()
var timezoneObjects: [TimezoneData] = [] var timezoneObjects: [TimezoneData] = []
@ -391,7 +392,8 @@ class NotesPopover: NSViewController {
timezone: model.timezone(), timezone: model.timezone(),
alertIndex: alertIndex, alertIndex: alertIndex,
reminderDate: reminderPicker.dateValue, reminderDate: reminderPicker.dateValue,
additionalNotes: model.note) { additionalNotes: model.note)
{
showSuccessMessage() showSuccessMessage()
} }
} }

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)
} }

16
Clocker/Panel/PanelController.swift

@ -6,18 +6,12 @@ import CoreLoggerKit
class PanelController: ParentPanelController { class PanelController: ParentPanelController {
@objc dynamic var hasActivePanel: Bool = false @objc dynamic var hasActivePanel: Bool = false
static var sharedWindow = PanelController(windowNibName: .panel)
@IBOutlet var backgroundView: BackgroundPanelView! @IBOutlet var backgroundView: BackgroundPanelView!
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
} }
class func shared() -> PanelController {
return sharedWindow
}
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
@ -81,6 +75,8 @@ class PanelController: ParentPanelController {
updateDefaultPreferences() updateDefaultPreferences()
setupUpcomingEventViewCollectionViewIfNeccesary()
if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false { if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false {
futureSliderView.isHidden = true futureSliderView.isHidden = true
modernContainerView.isHidden = true modernContainerView.isHidden = true
@ -154,7 +150,8 @@ 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
@ -221,7 +218,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")
@ -308,6 +305,9 @@ class PanelController: ParentPanelController {
window?.orderOut(nil) window?.orderOut(nil)
datasource = nil datasource = nil
upcomingEventsDataSource = nil
parentTimer?.pause()
parentTimer = nil
} }
func setActivePanel(newValue: Bool) { func setActivePanel(newValue: Bool) {

32
Clocker/Panel/ParentPanelController.swift

@ -195,9 +195,11 @@ class ParentPanelController: NSWindowController {
selector: #selector(systemTimezoneDidChange), selector: #selector(systemTimezoneDidChange),
name: NSNotification.Name.NSSystemTimeZoneDidChange, name: NSNotification.Name.NSSystemTimeZoneDidChange,
object: nil) object: nil)
NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification,
object: self, object: self,
queue: OperationQueue.main) { [weak self] _ in queue: OperationQueue.main)
{ [weak self] _ in
if let sSelf = self { if let sSelf = self {
sSelf.mainTableView.reloadData() sSelf.mainTableView.reloadData()
sSelf.setScrollViewConstraint() sSelf.setScrollViewConstraint()
@ -349,7 +351,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)
@ -425,7 +427,8 @@ class ParentPanelController: NSWindowController {
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
} }
} }
@ -582,7 +585,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 = MenubarTitleProvider() private lazy var menubarTitleHandler = MenubarTitleProvider(with: DataStore.shared())
@objc func updateTime() { @objc func updateTime() {
let store = DataStore.shared() let store = DataStore.shared()
@ -613,7 +616,8 @@ class ParentPanelController: NSWindowController {
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
} }
@ -633,7 +637,8 @@ 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
@ -733,7 +738,8 @@ class ParentPanelController: NSWindowController {
@IBAction func calendarButtonAction(_ sender: NSButton) { @IBAction func calendarButtonAction(_ sender: NSButton) {
if sender.title == NSLocalizedString("Click here to start.", if sender.title == NSLocalizedString("Click here to start.",
comment: "Button Title for no Calendar access") { comment: "Button Title for no Calendar access")
{
showPermissionsWindow() showPermissionsWindow()
} else { } else {
retrieveCalendarEvents() retrieveCalendarEvents()
@ -824,7 +830,8 @@ 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
@ -904,7 +911,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)
@ -940,7 +947,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" {
@ -1124,7 +1131,8 @@ extension ParentPanelController: NSSharingServicePickerDelegate {
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)

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

4
Clocker/Panel/UI/TimezoneCellView.swift

@ -199,7 +199,7 @@ class TimezoneCellView: NSTableCellView {
} }
override func mouseDown(with event: NSEvent) { override func mouseDown(with event: NSEvent) {
if (event.clickCount == 1) { if event.clickCount == 1 {
// Text is copied in the following format: Chicago - 1625185925 // Text is copied in the following format: Chicago - 1625185925
let clipboardCopy = "\(customName.stringValue) - \(time.stringValue)" let clipboardCopy = "\(customName.stringValue) - \(time.stringValue)"
let pasteboard = NSPasteboard.general let pasteboard = NSPasteboard.general
@ -209,7 +209,7 @@ class TimezoneCellView: NSTableCellView {
window?.contentView?.makeToast("Copied to Clipboard".localized()) window?.contentView?.makeToast("Copied to Clipboard".localized())
window?.endEditing(for: nil) window?.endEditing(for: nil)
} else if (event.clickCount == 2) { } else if event.clickCount == 2 {
// TODO: Favourite this timezone // TODO: Favourite this timezone
} }
} }

5
Clocker/Panel/Upcoming Events/UpcomingEventViewItem.swift

@ -40,7 +40,8 @@ class UpcomingEventViewItem: NSCollectionViewItem {
_ color: NSColor, _ color: NSColor,
_ link: URL?, _ link: URL?,
_ delegate: UpcomingEventPanelDelegate?, _ delegate: UpcomingEventPanelDelegate?,
_ isCancelled: Bool) { _ isCancelled: Bool)
{
if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint / 2 { if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint / 2 {
leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint / 2 leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint / 2
} }
@ -89,7 +90,7 @@ class UpcomingEventViewItem: NSCollectionViewItem {
func setupEmptyState() { func setupEmptyState() {
let subtitle = NSCalendar.autoupdatingCurrent.isDateInWeekend(Date()) ? NSLocalizedString("Happy Weekend.", comment: "Button Title for no upcoming event") : NSLocalizedString("Great going.", comment: "Button Title for no upcoming event") let subtitle = NSCalendar.autoupdatingCurrent.isDateInWeekend(Date()) ? NSLocalizedString("Happy Weekend.", comment: "Button Title for no upcoming event") : NSLocalizedString("Great going.", comment: "Button Title for no upcoming event")
setAlternateState(NSLocalizedString("No upcoming events for today 🎉!", comment: "Next Event Label with no upcoming event"), setAlternateState(NSLocalizedString("No upcoming events for today!", comment: "Next Event Label with no upcoming event"),
subtitle, subtitle,
NSColor.systemGreen, NSColor.systemGreen,
nil) nil)

2
Clocker/Preferences/About/PointingHandCursorButton.swift

@ -3,7 +3,7 @@
import Cocoa import Cocoa
class PointingHandCursorButton: NSButton { class PointingHandCursorButton: NSButton {
let pointingHandCursor: NSCursor = NSCursor.pointingHand let pointingHandCursor: NSCursor = .pointingHand
override func draw(_ dirtyRect: NSRect) { override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect) super.draw(dirtyRect)

5
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

@ -80,7 +80,8 @@ class AppFeedbackWindowController: NSWindowController {
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification,
object: nil, object: nil,
queue: OperationQueue.main) { _ in queue: OperationQueue.main)
{ _ in
self.window?.backgroundColor = Themer.shared().mainBackgroundColor() self.window?.backgroundColor = Themer.shared().mainBackgroundColor()
self.setup() self.setup()
} }
@ -201,7 +202,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(),
] ]
} }

22
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -16,6 +16,8 @@ class AppearanceViewController: ParentViewController {
@IBOutlet var includePlaceNameControl: NSSegmentedControl! @IBOutlet var includePlaceNameControl: NSSegmentedControl!
@IBOutlet var appearanceTab: NSTabView! @IBOutlet var appearanceTab: NSTabView!
@IBOutlet var appDisplayControl: NSSegmentedControl! @IBOutlet var appDisplayControl: NSSegmentedControl!
@IBOutlet var syncLabel: NSTextField!
@IBOutlet var syncSegementedControl: NSSegmentedControl!
private var themeDidChangeNotification: NSObjectProtocol? private var themeDidChangeNotification: NSObjectProtocol?
@ -60,7 +62,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 {
@ -134,6 +136,10 @@ class AppearanceViewController: ParentViewController {
// True is Menubar Only and False is Menubar + Dock // True is Menubar Only and False is Menubar + Dock
let appDisplayOptions = DataStore.shared().shouldDisplay(.appDisplayOptions) let appDisplayOptions = DataStore.shared().shouldDisplay(.appDisplayOptions)
appDisplayControl.setSelected(true, forSegment: appDisplayOptions ? 0 : 1) appDisplayControl.setSelected(true, forSegment: appDisplayOptions ? 0 : 1)
// Set the Sync value from NSUbiqutousKeyValueStore
let syncEnabled = NSUbiquitousKeyValueStore.default.bool(forKey: CLEnableSyncKey)
syncSegementedControl.setSelected(true, forSegment: syncEnabled ? 0 : 1)
} }
@IBOutlet var timeFormatLabel: NSTextField! @IBOutlet var timeFormatLabel: NSTextField!
@ -150,7 +156,6 @@ class AppearanceViewController: ParentViewController {
@IBOutlet var menubarModeLabel: NSTextField! @IBOutlet var menubarModeLabel: NSTextField!
@IBOutlet var previewLabel: NSTextField! @IBOutlet var previewLabel: NSTextField!
@IBOutlet var miscelleaneousLabel: NSTextField! @IBOutlet var miscelleaneousLabel: NSTextField!
@IBOutlet var dstTransitionField: NSTextField!
// Panel Preview // Panel Preview
@IBOutlet var previewPanelTableView: NSTableView! @IBOutlet var previewPanelTableView: NSTableView!
@ -162,6 +167,7 @@ class AppearanceViewController: ParentViewController {
showSliderLabel.stringValue = "Time Scroller".localized() showSliderLabel.stringValue = "Time Scroller".localized()
showSunriseLabel.stringValue = "Show Sunrise/Sunset".localized() showSunriseLabel.stringValue = "Show Sunrise/Sunset".localized()
largerTextLabel.stringValue = "Larger Text".localized() largerTextLabel.stringValue = "Larger Text".localized()
syncLabel.stringValue = "Enable iCloud Sync".localized()
futureSliderRangeLabel.stringValue = "Future Slider Range".localized() futureSliderRangeLabel.stringValue = "Future Slider Range".localized()
includeDateLabel.stringValue = "Include Date".localized() includeDateLabel.stringValue = "Include Date".localized()
includeDayLabel.stringValue = "Include Day".localized() includeDayLabel.stringValue = "Include Day".localized()
@ -172,9 +178,9 @@ class AppearanceViewController: ParentViewController {
[timeFormatLabel, panelTheme, [timeFormatLabel, panelTheme,
dayDisplayOptionsLabel, showSliderLabel, dayDisplayOptionsLabel, showSliderLabel,
showSunriseLabel, largerTextLabel, futureSliderRangeLabel, showSunriseLabel, largerTextLabel, syncLabel, futureSliderRangeLabel,
includeDayLabel, includeDateLabel, includePlaceLabel, appDisplayLabel, menubarModeLabel, includeDayLabel, includeDateLabel, includePlaceLabel, appDisplayLabel, menubarModeLabel,
previewLabel, miscelleaneousLabel, dstTransitionField].forEach { previewLabel, miscelleaneousLabel].forEach {
$0?.textColor = Themer.shared().mainTextColor() $0?.textColor = Themer.shared().mainTextColor()
} }
@ -188,7 +194,8 @@ 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()
@ -352,8 +359,9 @@ class AppearanceViewController: ParentViewController {
previewPanelTableView.reloadData() previewPanelTableView.reloadData()
} }
@IBAction func toggleDSTTransitionOption(_: Any) { @IBAction func toggleSync(_ sender: NSSegmentedControl) {
previewPanelTableView.reloadData() NSUbiquitousKeyValueStore.default.set(sender.selectedSegment == 0, forKey: CLEnableSyncKey)
DataStore.shared().setupSyncNotification()
} }
} }

27
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -5,29 +5,26 @@ import CoreLoggerKit
import EventKit import EventKit
class ClockerTextBackgroundView: NSView { class ClockerTextBackgroundView: NSView {
private var themeDidChangeNotification: NSObjectProtocol?
override func awakeFromNib() { override func awakeFromNib() {
wantsLayer = true wantsLayer = true
layer?.cornerRadius = 8.0 layer?.cornerRadius = 8.0
layer?.masksToBounds = false layer?.masksToBounds = false
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
}
deinit { NotificationCenter.default.addObserver(self,
if let themeDidChangeNotif = themeDidChangeNotification { selector: #selector(updateBackgroundColor),
NotificationCenter.default.removeObserver(themeDidChangeNotif) name: .themeDidChangeNotification,
} object: nil)
} }
override func updateLayer() { override func updateLayer() {
super.updateLayer() super.updateLayer()
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
} }
@objc func updateBackgroundColor() {
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
} }
class CalendarViewController: ParentViewController { class CalendarViewController: ParentViewController {
@ -113,7 +110,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)
@ -242,13 +239,15 @@ 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

7
Clocker/Preferences/General/PreferencesDataSource.swift

@ -104,7 +104,8 @@ 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,7 +162,7 @@ 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")
@ -174,7 +175,7 @@ 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")
} }

14
Clocker/Preferences/General/PreferencesViewController.swift

@ -93,9 +93,10 @@ class PreferencesViewController: ParentViewController {
name: NSNotification.Name.customLabelChanged, name: NSNotification.Name.customLabelChanged,
object: nil) object: nil)
NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification, NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification,
object: self, object: self,
queue: OperationQueue.main) { [weak self] _ in queue: OperationQueue.main)
{ [weak self] _ in
if let sSelf = self { if let sSelf = self {
sSelf.refreshTimezoneTableView() sSelf.refreshTimezoneTableView()
} }
@ -250,7 +251,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)!,
]) ])
} }
@ -348,7 +349,8 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
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)
} }
@ -512,7 +514,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))
@ -604,7 +606,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

10
Clocker/Preferences/General/SearchDataSource.swift

@ -102,10 +102,9 @@ class SearchDataSource: NSObject {
timezoneArray.append(utcTimezone) timezoneArray.append(utcTimezone)
for identifier in TimeZone.knownTimeZoneIdentifiers { for identifier in TimeZone.knownTimeZoneIdentifiers {
guard let timezoneObject = TimeZone(identifier: identifier) else { if let timezoneObject = TimeZone(identifier: identifier) {
continue // Force-cast explicity since we get the identifier from `knownTimeZoneIdentifiers`
} let abbreviation = timezoneObject.abbreviation()!
let abbreviation = timezoneObject.abbreviation() ?? "Empty"
let identifier = timezoneObject.identifier let identifier = timezoneObject.identifier
var tags: Set<String> = [abbreviation.lowercased(), identifier.lowercased()] var tags: Set<String> = [abbreviation.lowercased(), identifier.lowercased()]
var extraTags: [String] = [] var extraTags: [String] = []
@ -125,6 +124,7 @@ class SearchDataSource: NSObject {
timezoneArray.append(timezoneMetadata) timezoneArray.append(timezoneMetadata)
} }
} }
}
@discardableResult func calculateChangesets() -> Bool { @discardableResult func calculateChangesets() -> Bool {
var changesets: [RowType] = [] var changesets: [RowType] = []
@ -157,7 +157,7 @@ class SearchDataSource: NSObject {
timezoneFilteredArray = timezoneArray.filter { timezoneMetadata -> Bool in timezoneFilteredArray = timezoneArray.filter { timezoneMetadata -> Bool in
let tags = timezoneMetadata.tags let tags = timezoneMetadata.tags
for tag in tags where tag.contains(searchString) { for tag in tags where tag.contains(searchString.lowercased()) {
return true return true
} }
return false return false

13
Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift

@ -6,17 +6,24 @@ import CoreModelKit
import EventKit import EventKit
class MenubarTitleProvider: NSObject { class MenubarTitleProvider: NSObject {
private let store: DataStore
init(with dataStore: DataStore) {
store = dataStore
super.init()
}
func titleForMenubar() -> String? { func titleForMenubar() -> String? {
if let nextEvent = checkForUpcomingEvents() { if let nextEvent = checkForUpcomingEvents() {
return nextEvent return nextEvent
} }
guard let menubarTitles = DataStore.shared().menubarTimezones() else { guard let menubarTitles = store.menubarTimezones() else {
return nil return nil
} }
// If the menubar is in compact mode, we don't need any of the below calculations; exit early // If the menubar is in compact mode, we don't need any of the below calculations; exit early
if DataStore.shared().shouldDisplay(.menubarCompactMode) { if store.shouldDisplay(.menubarCompactMode) {
return nil return nil
} }
@ -35,7 +42,7 @@ class MenubarTitleProvider: NSObject {
} }
func checkForUpcomingEvents() -> String? { func checkForUpcomingEvents() -> String? {
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) { if store.shouldDisplay(.showMeetingInMenubar) {
let filteredDates = EventCenter.sharedCenter().eventsForDate let filteredDates = EventCenter.sharedCenter().eventsForDate
let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar let autoupdatingCal = EventCenter.sharedCenter().autoupdatingCalendar
guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else { guard let events = filteredDates[autoupdatingCal.startOfDay(for: Date())] else {

16
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -37,10 +37,12 @@ func compactWidth(for timezone: TimezoneData) -> Int {
if timeFormat == DateFormat.twelveHour if timeFormat == DateFormat.twelveHour
|| timeFormat == DateFormat.twelveHourWithSeconds || timeFormat == DateFormat.twelveHourWithSeconds
|| timeFormat == DateFormat.twelveHourWithZero || timeFormat == DateFormat.twelveHourWithZero
|| timeFormat == DateFormat.twelveHourWithSeconds { || timeFormat == DateFormat.twelveHourWithSeconds
{
totalWidth += 20 totalWidth += 20
} else if timeFormat == DateFormat.twentyFourHour } else if timeFormat == DateFormat.twentyFourHour
|| timeFormat == DateFormat.twentyFourHourWithSeconds { || timeFormat == DateFormat.twentyFourHourWithSeconds
{
totalWidth += 0 totalWidth += 0
} }
@ -81,7 +83,8 @@ class StatusContainerView: NSView {
if showUpcomingEventView, if showUpcomingEventView,
let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())], let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())],
events.isEmpty == false, events.isEmpty == false,
let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events) { let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events)
{
let calculatedWidth = bestWidth(for: upcomingEvent) let calculatedWidth = bestWidth(for: upcomingEvent)
let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30) let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30)
let calendarItemView = UpcomingEventStatusItemView(frame: frame) let calendarItemView = UpcomingEventStatusItemView(frame: frame)
@ -100,7 +103,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 {
@ -163,7 +166,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)
@ -185,7 +188,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 bestSize = compactModeTimeFont.size(eventInfo.metadataForMeeting(), let bestSize = compactModeTimeFont.size(eventInfo.metadataForMeeting(),
@ -236,6 +239,7 @@ class StatusContainerView: NSView {
// NSView move animation // NSView move animation
NSAnimationContext.runAnimationGroup({ context in NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2 context.duration = 0.2
context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
let newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: newWidth, height: frame.size.height) let newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: newWidth, height: frame.size.height)
// The view will animate to the new origin // The view will animate to the new origin
self.animator().frame = newFrame self.animator().frame = newFrame

26
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -22,7 +22,7 @@ class StatusItemHandler: NSObject {
return statusItem return statusItem
}() }()
private var menubarTitleHandler = MenubarTitleProvider() private var menubarTitleHandler = MenubarTitleProvider(with: DataStore.shared())
private var statusContainerView: StatusContainerView? private var statusContainerView: StatusContainerView?
@ -115,7 +115,8 @@ class StatusItemHandler: NSObject {
userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification, userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification,
object: self, object: self,
queue: mainQueue) { _ in queue: mainQueue)
{ _ in
self.setupStatusItem() self.setupStatusItem()
} }
@ -239,22 +240,33 @@ class StatusItemHandler: NSObject {
} }
func updateCompactMenubar() { func updateCompactMenubar() {
if let upcomingEvent = menubarTitleHandler.checkForUpcomingEvents() { if menubarTitleHandler.checkForUpcomingEvents() != nil {
print("Need to construct upcoming event view \(upcomingEvent)")
// Iterate and see if we're showing the calendar item view // Iterate and see if we're showing the calendar item view
let upcomingEventView = retrieveUpcomingEventStatusView() let upcomingEventView = retrieveUpcomingEventStatusView()
// If not, reconstruct Status Container View with another view // If not, reconstruct Status Container View with another view
if upcomingEventView == nil { if upcomingEventView == nil {
constructCompactView(with: true) constructCompactView(with: true)
} }
} else { } else if let upcomingEventView = retrieveUpcomingEventStatusView() {
let upcomingEventView = retrieveUpcomingEventStatusView() upcomingEventView.removeFromSuperview()
upcomingEventView?.removeFromSuperview() constructCompactView() // So that Status Container View reclaims the space
} }
// This will internally call `statusItemViewSetNeedsDisplay` on all subviews ensuring all text in the menubar is up-to-date. // This will internally call `statusItemViewSetNeedsDisplay` on all subviews ensuring all text in the menubar is up-to-date.
statusContainerView?.updateTime() statusContainerView?.updateTime()
} }
private func removeUpcomingStatusItemView() {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
let upcomingEventView = retrieveUpcomingEventStatusView()
upcomingEventView?.removeFromSuperview()
}) { [weak self] in
if let sSelf = self {
sSelf.constructCompactView()
}
}
}
func refresh() { func refresh() {
if currentState == .compactText { if currentState == .compactText {
updateCompactMenubar() updateCompactMenubar()

8
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,14 +91,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),
]) ])
} }

8
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,14 +54,14 @@ 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),
]) ])
} }

90
Clocker/Preferences/Preferences.storyboard

@ -791,8 +791,8 @@
<gridRow id="YtR-o1-nGp"/> <gridRow id="YtR-o1-nGp"/>
<gridRow id="vjb-Ch-BZs"/> <gridRow id="vjb-Ch-BZs"/>
<gridRow id="ad4-FM-AWq"/> <gridRow id="ad4-FM-AWq"/>
<gridRow id="yUG-1W-BQ2"/>
<gridRow id="fbk-ef-Req"/> <gridRow id="fbk-ef-Req"/>
<gridRow id="B74-Mt-1h8"/>
</rows> </rows>
<columns> <columns>
<gridColumn xPlacement="trailing" id="ARE-A4-4k4"/> <gridColumn xPlacement="trailing" id="ARE-A4-4k4"/>
@ -924,67 +924,66 @@
</connections> </connections>
</segmentedControl> </segmentedControl>
</gridCell> </gridCell>
<gridCell row="yUG-1W-BQ2" column="ARE-A4-4k4" id="M8P-5s-oL4"> <gridCell row="fbk-ef-Req" column="ARE-A4-4k4" id="Vth-MP-nfS">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="ZWy-WW-6H9"> <textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="xwt-pY-1w9">
<rect key="frame" x="50" y="42" width="163" height="18"/> <rect key="frame" x="9" y="43" width="204" height="18"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="159" id="NX0-Bo-SU8"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="7fj-Em-Lyh"/>
</constraints> </constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Show DST transition info" id="i0m-6h-scA"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Larger Text" id="LOM-yg-tI0">
<font key="font" size="13" name="Avenir-Light"/> <font key="font" size="13" name="Avenir-Light"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
</gridCell> </gridCell>
<gridCell row="yUG-1W-BQ2" column="YBI-pK-gPQ" id="N12-lC-Pe1"> <gridCell row="fbk-ef-Req" column="YBI-pK-gPQ" id="dCk-cz-no0">
<segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DCB-IB-UxK"> <slider key="contentView" verticalHuggingPriority="750" alphaValue="0.59999999999999998" translatesAutoresizingMaskIntoConstraints="NO" id="3cU-IS-3Qu">
<rect key="frame" x="229" y="39" width="81" height="24"/> <rect key="frame" x="229" y="36" width="104" height="28"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="PZZ-H7-LgV"> <sliderCell key="cell" state="on" alignment="left" minValue="4" maxValue="7" doubleValue="4" tickMarkPosition="above" numberOfTickMarks="4" allowsTickMarkValuesOnly="YES" sliderType="linear" id="eAh-k3-cof"/>
<font key="font" size="12" name="Avenir-Light"/>
<segments>
<segment label="Yes" width="36"/>
<segment label="No" width="36" selected="YES" tag="1"/>
</segments>
</segmentedCell>
<connections> <connections>
<action selector="toggleDSTTransitionOption:" target="1aL-zR-8L4" id="9bi-NL-gMv"/> <action selector="fontSliderChanged:" target="1aL-zR-8L4" id="YAW-aA-5aR"/>
<binding destination="Gpv-Gr-MxZ" name="selectedIndex" keyPath="values.com.abhishek.showDSTTransitionInfo" id="eG0-sL-5hE"/> <binding destination="Gpv-Gr-MxZ" name="value" keyPath="values.userFontSize" id="Dzw-Zc-qN5"/>
</connections> </connections>
</segmentedControl> </slider>
</gridCell> </gridCell>
<gridCell row="fbk-ef-Req" column="ARE-A4-4k4" id="Vth-MP-nfS"> <gridCell row="B74-Mt-1h8" column="ARE-A4-4k4" id="PbY-PK-2xB">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="xwt-pY-1w9"> <textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="kkn-L8-yOj">
<rect key="frame" x="9" y="1" width="204" height="18"/> <rect key="frame" x="9" y="2" width="204" height="18"/>
<constraints> <constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="7fj-Em-Lyh"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="eD6-Fv-2tz"/>
</constraints> </constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Larger Text" id="LOM-yg-tI0"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Enable iCloud Sync" id="MDa-w4-4Xw">
<font key="font" size="13" name="Avenir-Light"/> <font key="font" size="13" name="Avenir-Light"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
</gridCell> </gridCell>
<gridCell row="fbk-ef-Req" column="YBI-pK-gPQ" id="dCk-cz-no0"> <gridCell row="B74-Mt-1h8" column="YBI-pK-gPQ" id="kVc-I2-8Kp">
<slider key="contentView" verticalHuggingPriority="750" alphaValue="0.59999999999999998" translatesAutoresizingMaskIntoConstraints="NO" id="3cU-IS-3Qu"> <segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncK-Q6-ag9">
<rect key="frame" x="229" y="-6" width="104" height="28"/> <rect key="frame" x="229" y="-1" width="81" height="24"/>
<sliderCell key="cell" state="on" alignment="left" minValue="4" maxValue="7" doubleValue="4" tickMarkPosition="above" numberOfTickMarks="4" allowsTickMarkValuesOnly="YES" sliderType="linear" id="eAh-k3-cof"/> <segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="bio-ov-phY">
<font key="font" size="12" name="Avenir-Light"/>
<segments>
<segment label="Yes" width="36"/>
<segment label="No" width="36" selected="YES" tag="1"/>
</segments>
</segmentedCell>
<connections> <connections>
<action selector="fontSliderChanged:" target="1aL-zR-8L4" id="YAW-aA-5aR"/> <action selector="toggleSync:" target="1aL-zR-8L4" id="4Mg-ie-F7U"/>
<binding destination="Gpv-Gr-MxZ" name="value" keyPath="values.userFontSize" id="Dzw-Zc-qN5"/>
</connections> </connections>
</slider> </segmentedControl>
</gridCell> </gridCell>
</gridCells> </gridCells>
</gridView> </gridView>
<scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="113" horizontalPageScroll="10" verticalLineScroll="113" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ytg-0u-Mtu"> <scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="113" horizontalPageScroll="10" verticalLineScroll="113" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ytg-0u-Mtu">
<rect key="frame" x="70" y="39" width="400" height="100"/> <rect key="frame" x="70" y="39" width="400" height="100"/>
<clipView key="contentView" ambiguous="YES" id="gnX-f5-31D"> <clipView key="contentView" id="gnX-f5-31D">
<rect key="frame" x="1" y="1" width="398" height="98"/> <rect key="frame" x="1" y="1" width="398" height="98"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="111" rowSizeStyle="automatic" viewBased="YES" id="KbJ-p4-i6E"> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="111" rowSizeStyle="automatic" viewBased="YES" id="KbJ-p4-i6E">
<rect key="frame" x="0.0" y="0.0" width="412" height="113"/> <rect key="frame" x="0.0" y="0.0" width="412" height="113"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
@ -1468,7 +1467,6 @@
<outlet property="appDisplayLabel" destination="HNj-dh-8gr" id="A6U-gp-sje"/> <outlet property="appDisplayLabel" destination="HNj-dh-8gr" id="A6U-gp-sje"/>
<outlet property="appearanceTab" destination="SGu-yd-JQh" id="CJn-3E-Ujd"/> <outlet property="appearanceTab" destination="SGu-yd-JQh" id="CJn-3E-Ujd"/>
<outlet property="dayDisplayOptionsLabel" destination="gFE-hZ-J92" id="tVv-pC-MSW"/> <outlet property="dayDisplayOptionsLabel" destination="gFE-hZ-J92" id="tVv-pC-MSW"/>
<outlet property="dstTransitionField" destination="ZWy-WW-6H9" id="psJ-Wm-6Re"/>
<outlet property="futureSliderRangeLabel" destination="GaR-Qm-7u4" id="0aB-BK-7fN"/> <outlet property="futureSliderRangeLabel" destination="GaR-Qm-7u4" id="0aB-BK-7fN"/>
<outlet property="includeDateInMenubarControl" destination="Axn-Tb-Cdx" id="M5x-Qt-zvs"/> <outlet property="includeDateInMenubarControl" destination="Axn-Tb-Cdx" id="M5x-Qt-zvs"/>
<outlet property="includeDateLabel" destination="fTA-lS-4wJ" id="J1i-yl-JmT"/> <outlet property="includeDateLabel" destination="fTA-lS-4wJ" id="J1i-yl-JmT"/>
@ -1487,6 +1485,8 @@
<outlet property="showSliderLabel" destination="8Jv-Cf-blJ" id="UHb-hK-VVl"/> <outlet property="showSliderLabel" destination="8Jv-Cf-blJ" id="UHb-hK-VVl"/>
<outlet property="showSunriseLabel" destination="4lt-X6-3uU" id="tez-s5-RzL"/> <outlet property="showSunriseLabel" destination="4lt-X6-3uU" id="tez-s5-RzL"/>
<outlet property="sliderDayRangePopup" destination="8Nx-Xq-XDU" id="PBM-yB-Yo1"/> <outlet property="sliderDayRangePopup" destination="8Nx-Xq-XDU" id="PBM-yB-Yo1"/>
<outlet property="syncLabel" destination="kkn-L8-yOj" id="egW-Df-fnH"/>
<outlet property="syncSegementedControl" destination="ncK-Q6-ag9" id="8oT-zA-Ms5"/>
<outlet property="theme" destination="89w-KN-GJ6" id="SAL-Sh-eqp"/> <outlet property="theme" destination="89w-KN-GJ6" id="SAL-Sh-eqp"/>
<outlet property="timeFormat" destination="iiG-Xu-4id" id="oM3-1Y-fAF"/> <outlet property="timeFormat" destination="iiG-Xu-4id" id="oM3-1Y-fAF"/>
<outlet property="timeFormatLabel" destination="wtO-uL-QBf" id="udS-d6-Tep"/> <outlet property="timeFormatLabel" destination="wtO-uL-QBf" id="udS-d6-Tep"/>
@ -1513,7 +1513,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<searchField toolTip="Search a timezone" wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Dha-h9-Nd0"> <searchField toolTip="Search a timezone" wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Dha-h9-Nd0">
<rect key="frame" x="8" y="205" width="320" height="23"/> <rect key="frame" x="8" y="121" width="320" height="23"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" focusRingType="none" placeholderString="Enter a city, state, country name" usesSingleLineMode="YES" maximumRecents="5" id="ikU-Tm-0WZ"> <searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" focusRingType="none" placeholderString="Enter a city, state, country name" usesSingleLineMode="YES" maximumRecents="5" id="ikU-Tm-0WZ">
<font key="font" size="13" name="Avenir-Light"/> <font key="font" size="13" name="Avenir-Light"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -1570,13 +1570,13 @@ DQ
</connections> </connections>
</button> </button>
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="32" horizontalPageScroll="10" verticalLineScroll="32" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0wY-ff-FLW"> <scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="32" horizontalPageScroll="10" verticalLineScroll="32" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0wY-ff-FLW">
<rect key="frame" x="8" y="30" width="320" height="165"/> <rect key="frame" x="8" y="30" width="320" height="81"/>
<clipView key="contentView" drawsBackground="NO" id="rGc-3M-cCq"> <clipView key="contentView" drawsBackground="NO" id="rGc-3M-cCq">
<rect key="frame" x="0.0" y="0.0" width="320" height="165"/> <rect key="frame" x="0.0" y="0.0" width="320" height="81"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" rowHeight="30" rowSizeStyle="automatic" viewBased="YES" id="xkl-2X-ZCb"> <tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" rowHeight="30" rowSizeStyle="automatic" viewBased="YES" id="xkl-2X-ZCb">
<rect key="frame" x="0.0" y="0.0" width="320" height="165"/> <rect key="frame" x="0.0" y="0.0" width="320" height="81"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -1666,14 +1666,14 @@ DQ
</scroller> </scroller>
</scrollView> </scrollView>
<progressIndicator wantsLayer="YES" focusRingType="none" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="0A5-gp-lay"> <progressIndicator wantsLayer="YES" focusRingType="none" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="0A5-gp-lay">
<rect key="frame" x="160" y="132" width="16" height="16"/> <rect key="frame" x="160" y="90" width="16" height="16"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="16" id="fgE-77-Vda"/> <constraint firstAttribute="height" constant="16" id="fgE-77-Vda"/>
<constraint firstAttribute="width" constant="16" id="pwe-em-e0a"/> <constraint firstAttribute="width" constant="16" id="pwe-em-e0a"/>
</constraints> </constraints>
</progressIndicator> </progressIndicator>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xgb-wU-8RU"> <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xgb-wU-8RU">
<rect key="frame" x="18" y="102" width="300" height="22"/> <rect key="frame" x="18" y="60" width="300" height="22"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="22" id="zqt-d8-yas"/> <constraint firstAttribute="height" constant="22" id="zqt-d8-yas"/>
</constraints> </constraints>
@ -1721,7 +1721,7 @@ DQ
<rect key="frame" x="0.0" y="484" width="613" height="30"/> <rect key="frame" x="0.0" y="484" width="613" height="30"/>
<subviews> <subviews>
<button toolTip="Sorts by time difference from your current timezone" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6d-qq-ycq"> <button toolTip="Sorts by time difference from your current timezone" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6d-qq-ycq">
<rect key="frame" x="285" y="3" width="8" height="25"/> <rect key="frame" x="283" y="3" width="9" height="25"/>
<constraints> <constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="180" id="cAs-on-f7X"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="180" id="cAs-on-f7X"/>
<constraint firstAttribute="height" constant="25" id="juv-QL-vMx"/> <constraint firstAttribute="height" constant="25" id="juv-QL-vMx"/>
@ -1736,7 +1736,7 @@ DQ
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0HL-uj-s4v"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0HL-uj-s4v">
<rect key="frame" x="331" y="3" width="8" height="25"/> <rect key="frame" x="331" y="3" width="9" height="25"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="25" id="eZL-Gr-38S"/> <constraint firstAttribute="height" constant="25" id="eZL-Gr-38S"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="120" id="sJk-T7-7Lm"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="120" id="sJk-T7-7Lm"/>
@ -1751,7 +1751,7 @@ DQ
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6fs-Mx-NcG"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6fs-Mx-NcG">
<rect key="frame" x="308" y="3" width="8" height="25"/> <rect key="frame" x="307" y="3" width="9" height="25"/>
<constraints> <constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="190" id="241-Rn-G6N"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="190" id="241-Rn-G6N"/>
<constraint firstAttribute="height" constant="25" id="weP-ll-vZ8"/> <constraint firstAttribute="height" constant="25" id="weP-ll-vZ8"/>
@ -2010,7 +2010,7 @@ CA
<image name="ClockerIcon-512" width="1024" height="1024"/> <image name="ClockerIcon-512" width="1024" height="1024"/>
<image name="CurrentLocation" width="350" height="350"/> <image name="CurrentLocation" width="350" height="350"/>
<image name="Extra" width="700" height="700"/> <image name="Extra" width="700" height="700"/>
<image name="NSDescendingSortIndicator" width="8" height="8"/> <image name="NSDescendingSortIndicator" width="9" height="9"/>
<image name="NSInfo" width="32" height="32"/> <image name="NSInfo" width="32" height="32"/>
<image name="NSPreferencesGeneral" width="32" height="32"/> <image name="NSPreferencesGeneral" width="32" height="32"/>
<image name="Privacy" width="350" height="350"/> <image name="Privacy" width="350" height="350"/>

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