Browse Source

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

pull/113/head
Abhishek Banthia 3 years ago
parent
commit
be5e987df8
  1. 11
      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. 73
      Clocker/ClockerUnitTests/AppDelegateTests.swift
  23. 18
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  24. 6
      Clocker/ClockerUnitTests/DateFormatterManagerTests.swift
  25. 75
      Clocker/ClockerUnitTests/ReviewControllerTests.swift
  26. 139
      Clocker/ClockerUnitTests/SearchDataSourceTests.swift
  27. 93
      Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift
  28. 20
      Clocker/ClockerUnitTests/ThemerTests.swift
  29. 6
      Clocker/CoreLoggerKit/Package.swift
  30. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/CoreLoggerKitTests.swift
  31. 2
      Clocker/CoreLoggerKit/Tests/CoreLoggerKitTests/XCTestManifests.swift
  32. 8
      Clocker/CoreModelKit/Package.swift
  33. 2
      Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift
  34. 2
      Clocker/CoreModelKit/Tests/CoreModelKitTests/XCTestManifests.swift
  35. 43
      Clocker/Dependencies/Date Additions/Date+Inits.swift
  36. 667
      Clocker/Dependencies/Date Additions/TimePeriod.swift
  37. 177
      Clocker/Dependencies/Date Additions/TimePeriodChain.swift
  38. 267
      Clocker/Dependencies/Date Additions/TimePeriodCollection.swift
  39. 146
      Clocker/Dependencies/Date Additions/TimePeriodGroup.swift
  40. 11
      Clocker/Events and Reminders/CalendarHandler.swift
  41. 3
      Clocker/Events and Reminders/RemindersHandler.swift
  42. 10
      Clocker/Menu Bar/StatusContainerView.swift
  43. 3
      Clocker/Menu Bar/StatusItemHandler.swift
  44. 8
      Clocker/Menu Bar/StatusItemView.swift
  45. 7
      Clocker/Onboarding/OnboardingSearchController.swift
  46. 4
      Clocker/Overall App/ConfigExport.swift
  47. 43
      Clocker/Overall App/DataStore.swift
  48. 1
      Clocker/Overall App/DateFormatterManager.swift
  49. 1
      Clocker/Overall App/Strings.swift
  50. 3
      Clocker/Overall App/Themer.swift
  51. 2
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  52. 10
      Clocker/Panel/Notes Popover/NotesPopover.swift
  53. 2
      Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift
  54. 16
      Clocker/Panel/PanelController.swift
  55. 40
      Clocker/Panel/ParentPanelController.swift
  56. 2
      Clocker/Panel/UI/PanelTableView.swift
  57. 6
      Clocker/Panel/UI/TimezoneCellView.swift
  58. 5
      Clocker/Panel/Upcoming Events/UpcomingEventViewItem.swift
  59. 2
      Clocker/Preferences/About/PointingHandCursorButton.swift
  60. 5
      Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift
  61. 22
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  62. 29
      Clocker/Preferences/Calendar/CalendarViewController.swift
  63. 7
      Clocker/Preferences/General/PreferencesDataSource.swift
  64. 16
      Clocker/Preferences/General/PreferencesViewController.swift
  65. 42
      Clocker/Preferences/General/SearchDataSource.swift
  66. 13
      Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift
  67. 22
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  68. 30
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift
  69. 8
      Clocker/Preferences/Menu Bar/StatusItemView.swift
  70. 8
      Clocker/Preferences/Menu Bar/UpcomingEventStatusItemView.swift
  71. 90
      Clocker/Preferences/Preferences.storyboard
  72. 6
      Clocker/StartupKit/Package.swift

11
Clocker/AppDelegate.swift

@ -8,7 +8,7 @@ import FirebaseCrashlytics
open class AppDelegate: NSObject, NSApplicationDelegate {
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 panelObserver: NSKeyValueObservation?
@ -84,7 +84,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
let floatingWindow = FloatingWindowController.shared()
floatingWindow.openPreferences(NSButton())
} else {
let panelController = PanelController.shared()
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) {
NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert()
@ -261,7 +254,7 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
open func invalidateMenubarTimer(_ showIcon: Bool) {
statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true)
}
private func setupPanelObserverIfNeeeded() {
if panelObserver == nil {
panelObserver = panelController.observe(\.hasActivePanel, options: [.new]) { obj, _ in

20
Clocker/Clocker.xcodeproj/project.pbxproj

@ -53,6 +53,7 @@
35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */; };
35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */; };
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */; };
35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */; };
357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* TimeMarkerViewItem.swift */; };
357391882507277500D30819 /* HourMarkerViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357391862507277500D30819 /* HourMarkerViewItem.xib */; };
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 */; };
35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EF022595F14002FA5C6 /* OnboardingParentViewController.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 */; };
35C36F0F225961DA002FA5C6 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EFF225961D9002FA5C6 /* TimePeriodCollection.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 */; };
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 */; };
@ -85,7 +83,6 @@
35C36F19225961DA002FA5C6 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F09225961DA002FA5C6 /* Enums.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 */; };
35C36F1C225961DA002FA5C6 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F0C225961DA002FA5C6 /* TimePeriodChain.swift */; };
35C36F2022596253002FA5C6 /* OneWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1E22596253002FA5C6 /* OneWindowController.swift */; };
35C36F2122596253002FA5C6 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F1F22596253002FA5C6 /* AppearanceViewController.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>"; };
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>"; };
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>"; };
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>"; };
@ -311,11 +309,8 @@
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -326,7 +321,6 @@
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>"; };
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>"; };
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>"; };
@ -598,10 +592,6 @@
35C36F09225961DA002FA5C6 /* Enums.swift */,
35C36F04225961DA002FA5C6 /* Integer+DateTools.swift */,
35C36F05225961DA002FA5C6 /* TimeChunk.swift */,
35C36EFD225961D9002FA5C6 /* TimePeriod.swift */,
35C36F0C225961DA002FA5C6 /* TimePeriodChain.swift */,
35C36EFF225961D9002FA5C6 /* TimePeriodCollection.swift */,
35C36F01225961D9002FA5C6 /* TimePeriodGroup.swift */,
);
path = "Date Additions";
sourceTree = "<group>";
@ -890,6 +880,7 @@
35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */,
35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */,
35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */,
35621CFB27F66C1900926D5C /* SearchDataSourceTests.swift */,
);
path = ClockerUnitTests;
sourceTree = "<group>";
@ -1279,6 +1270,7 @@
35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */,
35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */,
9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */,
35621CFC27F66C1900926D5C /* SearchDataSourceTests.swift in Sources */,
35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */,
C20839CA21515C1E00C86589 /* ClockerUnitTests.swift in Sources */,
);
@ -1354,7 +1346,6 @@
35C36F712259E185002FA5C6 /* NoTimezoneView.swift in Sources */,
35C36F2B2259D6FA002FA5C6 /* ParentPanelController.swift in Sources */,
35C36F582259DD8A002FA5C6 /* PanelTableView.swift in Sources */,
35C36F0F225961DA002FA5C6 /* TimePeriodCollection.swift in Sources */,
35C36F18225961DA002FA5C6 /* Date+Comparators.swift in Sources */,
353B5BC52698B78A0023858D /* UpcomingEventStatusItemView.swift in Sources */,
35C36FA02259ED6D002FA5C6 /* CalendarHandler.swift in Sources */,
@ -1374,12 +1365,9 @@
9AB6F1672259D23200A44663 /* PermissionsViewController.swift in Sources */,
3548C46126BEEFE400AFB533 /* UpcomingEventViewItem.swift in Sources */,
9AB6F1642259D1B900A44663 /* ParentViewController.swift in Sources */,
35C36F1C225961DA002FA5C6 /* TimePeriodChain.swift in Sources */,
3508CCAA259A0027000E3530 /* StatusContainerView.swift in Sources */,
35C36F11225961DA002FA5C6 /* TimePeriodGroup.swift in Sources */,
35C36EF922595F14002FA5C6 /* OnboardingParentViewController.swift in Sources */,
35C36F4E2259D981002FA5C6 /* DateFormatterManager.swift in Sources */,
35C36F0D225961DA002FA5C6 /* TimePeriod.swift in Sources */,
35C36EFB2259616B002FA5C6 /* Solar.swift in Sources */,
35C36F662259DF4C002FA5C6 /* UpcomingEventView.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">
<plist version="1.0">
<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>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>

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

@ -158,3 +158,6 @@
"New Zealand" = "New Zealand";
"Florida" = "Florida";
"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";
"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.";
"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";
"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.";
"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";
// 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.";
"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";
"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.";
"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";
"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.";
"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";
"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.";
"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";
"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.";
"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";
"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.";
"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 events for today!" = "No upcoming events for today!";
"No upcoming events for today!" = "No upcoming events for today 🎉";
/* Onboarding */
"Open Clocker At Login" = "ログイン時に Clocker を開始";
@ -355,3 +355,6 @@
"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";
"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.";
"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";
"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.";
"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";
"Florida" = "Floryda";
"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";
"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.";
"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";
"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.";
"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";
"Florida" = "Florida";
"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";
"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.";
"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 events for today!" = "No upcoming events for today!";
"No upcoming events for today!" = "No upcoming events for today 🎉";
/* Onboarding */
"Open Clocker At Login" = "在登入時打開 Clocker";
@ -355,3 +355,6 @@
"start-at-login" = "在登入時啟動";
// iCloud
"Enable iCloud Sync" = "Enable iCloud Sync";

73
Clocker/ClockerUnitTests/AppDelegateTests.swift

@ -1,12 +1,11 @@
// Copyright © 2015 Abhishek Banthia
import XCTest
import CoreModelKit
import XCTest
@testable import Clocker
class AppDelegateTests: XCTestCase {
func testStatusItemIsInitialized() throws {
let subject = NSApplication.shared.delegate as? AppDelegate
let statusHandler = subject?.statusItemForPanel()
@ -18,58 +17,58 @@ class AppDelegateTests: XCTestCase {
let subject = NSApplication.shared.delegate as? AppDelegate
let dockMenu = subject?.applicationDockMenu(NSApplication.shared)
let items = dockMenu?.items ?? []
XCTAssertEqual(dockMenu?.title, "Quick Access")
XCTAssertEqual(items.first?.title, "Toggle Panel")
XCTAssertEqual(items[1].title, "Preferences")
XCTAssertEqual(items[1].keyEquivalent, ",")
XCTAssertEqual(items[2].title, "Hide from Dock")
// Test selections
XCTAssertEqual(items.first?.action, #selector(AppDelegate.togglePanel(_:)))
XCTAssertEqual(items[2].action, #selector(AppDelegate.hideFromDock))
items.forEach { menuItem in
XCTAssertTrue(menuItem.isEnabled)
}
}
func testSetupMenubarTimer() {
let subject = NSApplication.shared.delegate as? AppDelegate
let statusItemHandler = subject?.statusItemForPanel()
XCTAssertEqual(statusItemHandler?.statusItem.autosaveName, NSStatusItem.AutosaveName("ClockerStatusItem"))
}
func testFloatingWindow() {
let subject = NSApplication.shared.delegate as? AppDelegate
let previousWindows = NSApplication.shared.windows
XCTAssertTrue(previousWindows.count >= 1) // Only the status bar window should be present
subject?.setupFloatingWindow(true)
let floatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) {
if (window.windowController as? FloatingWindowController) != nil {
return true
}
return false
}
XCTAssertNotNil(floatingWindow)
XCTAssertEqual(floatingWindow?.windowController?.windowFrameAutosaveName, NSWindow.FrameAutosaveName("FloatingWindowAutoSave"))
subject?.setupFloatingWindow(false)
let closedFloatingWindow = NSApplication.shared.windows.first { window in
if ((window.windowController as? FloatingWindowController) != nil) {
if (window.windowController as? FloatingWindowController) != nil {
return true
}
return false
}
XCTAssertNotNil(closedFloatingWindow)
}
func testActivationPolicy() {
let subject = NSApplication.shared.delegate as? AppDelegate
let previousOption = UserDefaults.standard.integer(forKey: CLAppDisplayOptions)
@ -78,11 +77,11 @@ class AppDelegateTests: XCTestCase {
} else {
XCTAssertEqual(NSApp.activationPolicy(), .regular)
}
subject?.hideFromDock()
XCTAssertEqual(NSApp.activationPolicy(), .accessory)
}
func testMenubarInvalidationToIcon() {
let subject = NSApplication.shared.delegate as? AppDelegate
subject?.invalidateMenubarTimer(true)
@ -93,10 +92,10 @@ class AppDelegateTests: XCTestCase {
XCTAssertEqual(statusItemHandler?.statusItem.button?.imagePosition, .imageOnly)
XCTAssertEqual(statusItemHandler?.statusItem.toolTip, "Clocker")
}
func testCompactModeMenubarSetup() {
let subject = NSApplication.shared.delegate as? AppDelegate
let timezone1 = TimezoneData()
timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier
timezone1.formattedAddress = "MenubarTimezone"
@ -104,23 +103,23 @@ class AppDelegateTests: XCTestCase {
// Encode it in UserDefaults
let encodedTimezone = NSKeyedArchiver.archivedData(withRootObject: timezone1)
DataStore.shared().setTimezones([encodedTimezone])
subject?.setupMenubarTimer()
let statusItemHandler = subject?.statusItemForPanel()
XCTAssertNotNil(statusItemHandler?.statusItem.view) // This won't be nil for compact mode
DataStore.shared().setTimezones([])
}
func testStandardModeMenubarSetup() {
UserDefaults.standard.set(1, forKey: CLMenubarCompactMode) // Set the menubar mode to standard
let subject = NSApplication.shared.delegate as? AppDelegate
let statusItemHandler = subject?.statusItemForPanel()
subject?.setupMenubarTimer()
XCTAssertEqual(statusItemHandler?.statusItem.button?.image?.name(), "LightModeIcon")
let timezone1 = TimezoneData()
timezone1.timezoneID = TimeZone.autoupdatingCurrent.identifier
timezone1.formattedAddress = "MenubarTimezone"
@ -128,27 +127,13 @@ class AppDelegateTests: XCTestCase {
// Encode it in UserDefaults
let encodedTimezone = NSKeyedArchiver.archivedData(withRootObject: timezone1)
DataStore.shared().setTimezones([encodedTimezone])
subject?.setupMenubarTimer()
XCTAssertNil(subject?.statusItemForPanel().statusItem.view) // This will be nil for standard mode
DataStore.shared().setTimezones([])
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)
}
}

18
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -173,7 +173,7 @@ class ClockerUnitTests: XCTestCase {
XCTAssertTrue(operations.timeDifference() == ", 9h 30m ahead", "Difference was unexpectedly: \(operations.timeDifference())")
XCTAssertTrue(californiaOperations.timeDifference() == ", 3h behind", "Difference was unexpectedly: \(californiaOperations.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())")
}
@ -392,30 +392,28 @@ class ClockerUnitTests: XCTestCase {
XCTAssertNotNil(convertedDate)
}
}
func testStringFiltering() {
let stringWithComma = "Mumbai, Maharashtra"
let stringWithoutComma = "Mumbai"
let emptyString = ""
XCTAssertEqual(stringWithComma.filteredName(), "Mumbai")
XCTAssertEqual(stringWithoutComma.filteredName(), "Mumbai")
XCTAssertEqual(emptyString.filteredName(), "")
}
func testToasty() {
let view = NSView(frame: CGRect.zero)
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 result = XCTWaiter.wait(for: [toastExpectation], timeout: 1.5) // Set 1.5 seconds here for a little leeway
if result == XCTWaiter.Result.timedOut {
XCTAssertTrue(view.subviews.isEmpty)
}
}
}
func testPointingHandButton() {
let sampleRect = CGRect(x: 0, y: 0, width: 200, height: 200)
let pointingHandCursorButton = PointingHandCursorButton(frame: CGRect.zero)
@ -423,7 +421,7 @@ class ClockerUnitTests: XCTestCase {
pointingHandCursorButton.resetCursorRects()
XCTAssertEqual(pointingHandCursorButton.pointingHandCursor, NSCursor.pointingHand)
}
func testNoTimezoneView() {
let sampleRect = CGRect(x: 0, y: 0, width: 200, height: 200)
let subject = NoTimezoneView(frame: sampleRect)

6
Clocker/ClockerUnitTests/DateFormatterManagerTests.swift

@ -5,7 +5,6 @@ import XCTest
@testable import Clocker
class DateFormatterManagerTests: XCTestCase {
func testRegularDateFormatter() throws {
let subject = DateFormatterManager.dateFormatter(with: .medium, for: "UTC")
XCTAssertEqual(subject.dateStyle, .medium)
@ -13,7 +12,7 @@ class DateFormatterManagerTests: XCTestCase {
XCTAssertEqual(subject.locale.identifier, "en_US")
XCTAssertEqual(subject.timeZone.identifier, "GMT")
}
func testDateFormatterWithFormat() throws {
let subject = DateFormatterManager.dateFormatterWithFormat(with: .none, format: "hh:mm a", timezoneIdentifier: "Asia/Calcutta")
XCTAssertEqual(subject.dateStyle, .none)
@ -23,7 +22,7 @@ class DateFormatterManagerTests: XCTestCase {
XCTAssertEqual(subject.locale.identifier, "en_US")
XCTAssertEqual(subject.dateFormat, "hh:mm a")
}
func testLocalizedDateFormatter() throws {
let subject = DateFormatterManager.localizedFormatter(with: "hh:mm:ss", for: "America/Los_Angeles")
XCTAssertEqual(subject.dateStyle, .none)
@ -32,5 +31,4 @@ class DateFormatterManagerTests: XCTestCase {
XCTAssertEqual(subject.timeZone.identifier, "America/Los_Angeles")
XCTAssertEqual(subject.dateFormat, "hh:mm:ss")
}
}

75
Clocker/ClockerUnitTests/ReviewControllerTests.swift

@ -1,7 +1,7 @@
// Copyright © 2015 Abhishek Banthia
import XCTest
@testable import Clocker
import XCTest
class ReviewControllerTests: XCTestCase {
func testDebuggingMode() throws {
@ -9,14 +9,14 @@ class ReviewControllerTests: XCTestCase {
return
}
ReviewController.applicationDidLaunch(mockDefaults)
// Call it again to ensure Keys.install
ReviewController.applicationDidLaunch(mockDefaults)
ReviewController.setPreviewMode(true)
XCTAssertTrue(ReviewController.canPrompt())
}
func testPromptNotDisplayedInFirstWeekSinceInstall() {
guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker1") else {
return
@ -25,10 +25,10 @@ class ReviewControllerTests: XCTestCase {
ReviewController.applicationDidLaunch(mockDefaults)
// Explicitly set preview mode to false
ReviewController.setPreviewMode(false)
XCTAssertFalse(ReviewController.canPrompt())
}
func testPrompDisplayedAfterFirstWeekOfInstall() {
let dateChunk = TimeChunk(seconds: 0,
minutes: 0,
@ -38,21 +38,21 @@ class ReviewControllerTests: XCTestCase {
months: 0,
years: 0)
let oldDate = Date().subtract(dateChunk)
guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker2") else {
return
}
mockDefaults.set(oldDate, forKey: "install")
ReviewController.applicationDidLaunch(mockDefaults)
// Explicitly set preview mode to false
ReviewController.setPreviewMode(false)
XCTAssertNil(mockDefaults.object(forKey:"last-prompt"))
XCTAssertNil(mockDefaults.object(forKey:"last-version"))
XCTAssertNil(mockDefaults.object(forKey: "last-prompt"))
XCTAssertNil(mockDefaults.object(forKey: "last-version"))
XCTAssertTrue(ReviewController.canPrompt())
}
func testPromptDisplayAfterTwoMonths() {
let dateChunk = TimeChunk(seconds: 0,
minutes: 0,
@ -62,16 +62,16 @@ class ReviewControllerTests: XCTestCase {
months: 0,
years: 0)
let minInstall = Date().subtract(dateChunk)
let promptChunk = TimeChunk(seconds: 0,
minutes: 0,
hours: 0,
days: 60,
weeks: 0,
months: 0,
years: 0)
minutes: 0,
hours: 0,
days: 60,
weeks: 0,
months: 0,
years: 0)
let lastPromptDate = Date().subtract(promptChunk)
guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker3") else {
return
}
@ -79,12 +79,12 @@ class ReviewControllerTests: XCTestCase {
mockDefaults.set("test-version", forKey: "last-version")
mockDefaults.set(lastPromptDate, forKey: "last-prompt")
ReviewController.applicationDidLaunch(mockDefaults)
// Explicitly set preview mode to false
ReviewController.setPreviewMode(false)
XCTAssertFalse(ReviewController.canPrompt())
}
func testPrompDisplayedAfterThreeMonths() {
let dateChunk = TimeChunk(seconds: 0,
minutes: 0,
@ -94,16 +94,16 @@ class ReviewControllerTests: XCTestCase {
months: 0,
years: 0)
let minInstall = Date().subtract(dateChunk)
let promptChunk = TimeChunk(seconds: 0,
minutes: 0,
hours: 0,
days: 91,
weeks: 0,
months: 0,
years: 0)
minutes: 0,
hours: 0,
days: 91,
weeks: 0,
months: 0,
years: 0)
let lastPromptDate = Date().subtract(promptChunk)
guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker4") else {
return
}
@ -111,23 +111,22 @@ class ReviewControllerTests: XCTestCase {
mockDefaults.set("test-version", forKey: "last-version")
mockDefaults.set(lastPromptDate, forKey: "last-prompt")
ReviewController.applicationDidLaunch(mockDefaults)
// Explicitly set preview mode to false
ReviewController.setPreviewMode(false)
XCTAssertNotNil(mockDefaults.object(forKey:"last-prompt"))
XCTAssertNotNil(mockDefaults.object(forKey:"last-version"))
XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt"))
XCTAssertNotNil(mockDefaults.object(forKey: "last-version"))
XCTAssertTrue(ReviewController.canPrompt())
}
func testPrompted() {
guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker5") else {
return
}
ReviewController.applicationDidLaunch(mockDefaults)
ReviewController.prompt()
XCTAssertNotNil(mockDefaults.object(forKey:"last-prompt"))
XCTAssertNotNil(mockDefaults.object(forKey:"last-version"))
XCTAssertNotNil(mockDefaults.object(forKey: "last-prompt"))
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",
"longitude": "72.8776559"]
func testValidStandardMenubarHandler_returnMenubarTitle() {
private func makeMockStore(with menubarMode: Int = 1) -> DataStore {
// 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
let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 1
let operationsObject = TimezoneDataOperations(with: dataObject)
operationsObject.saveObject()
let menubarTimezones = DataStore.shared().menubarTimezones()
XCTAssertTrue(menubarTimezones?.count == 1)
saveObject(object: dataObject, in: store)
// 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)
let menubarTimezones = store.menubarTimezones()
XCTAssertTrue(menubarTimezones?.count == 1, "Count is \(String(describing: menubarTimezones?.count))")
}
func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() {
let store = makeMockStore()
// Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil)
store.setTimezones(nil)
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 0
let operationsObject = TimezoneDataOperations(with: dataObject)
operationsObject.saveObject()
saveObject(object: dataObject, in: store)
let menubarTimezones = DataStore.shared().menubarTimezones()
let menubarTimezones = store.menubarTimezones()
XCTAssertTrue(menubarTimezones?.count == 0)
}
func testUnfavouritedTimezone_returnNilMenubarString() {
let store = makeMockStore()
// Wipe all timezones from UserDefaults
DataStore.shared().setTimezones(nil)
let menubarHandler = MenubarTitleProvider()
store.setTimezones(nil)
let menubarHandler = MenubarTitleProvider(with: store)
let emptyMenubarString = menubarHandler.titleForMenubar()
// Returns early because DataStore.menubarTimezones is nil
XCTAssertNil(emptyMenubarString)
@ -66,12 +75,42 @@ class StandardMenubarHandlerTests: XCTestCase {
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
dataObject.isFavourite = 0
let operationsObject = TimezoneDataOperations(with: dataObject)
operationsObject.saveObject()
saveObject(object: dataObject, in: store)
let menubarString = menubarHandler.titleForMenubar() ?? ""
// Test menubar string is absent
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())
}
}

20
Clocker/ClockerUnitTests/ThemerTests.swift

@ -38,22 +38,22 @@ class ThemerTests: XCTestCase {
let expectedForwardsImage: String
let expectedResetSliderImage: String
}
@available(macOS 10.14, *)
func testSettingTheme() {
// Set to some random number should set to 0
let subject = Themer(index: 124)
XCTAssertEqual(NSAppearance(named: .aqua), NSAppearance(named: .aqua))
// Set the same theme; this should return early
subject.set(theme: 0)
// Set the theme to dark theme
subject.set(theme: 1)
let expectedApperance = NSAppearance(named: .darkAqua)
XCTAssertEqual(expectedApperance, NSApp.appearance)
}
func testLightTheme() throws {
let subject = Themer(index: 0) // 0 is for light theme
let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9),
@ -85,7 +85,7 @@ class ThemerTests: XCTestCase {
expectedResetSliderImage: "xmark.circle.fill")
testSubject(subject: subject, withExpectatations: expectedThemeElements)
}
func testDarkTheme() throws {
let subject = Themer(index: 1) // 1 is for dark theme
let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9),
@ -119,10 +119,10 @@ class ThemerTests: XCTestCase {
testSubject(subject: subject, withExpectatations: expectedThemeElements)
XCTAssertEqual(subject.description, "Current Theme is \(Themer.Theme.dark)")
}
func testSystemTheme() throws {
let currentSystemTheme =
UserDefaults.standard.string(forKey: CLAppleInterfaceStyleKey)?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light
UserDefaults.standard.string(forKey: CLAppleInterfaceStyleKey)?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light
let subject = Themer(index: 2) // 2 is for system theme
let expectedSliderKnobColor = currentSystemTheme == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9)
let expectedSliderRightColor = currentSystemTheme == .dark ? NSColor.white : NSColor.gray
@ -160,7 +160,7 @@ class ThemerTests: XCTestCase {
testSubject(subject: subject, withExpectatations: expectedThemeElements)
XCTAssertEqual(subject.description, "System Theme is \(currentSystemTheme == .dark ? Themer.Theme.dark : Themer.Theme.light)")
}
func testSolarizedLightTheme() throws {
let subject = Themer(index: 3) // 3 is for solarized light theme
let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9),
@ -193,7 +193,7 @@ class ThemerTests: XCTestCase {
expectedResetSliderImage: "xmark.circle.fill")
testSubject(subject: subject, withExpectatations: expectedThemeElements)
}
func testSolarizedDarkTheme() throws {
let subject = Themer(index: 4) // 4 is for solarized dark theme
let expectedThemeElements = ThemeExpectations(expectedSliderKnobColor: NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9),
@ -226,7 +226,7 @@ class ThemerTests: XCTestCase {
expectedResetSliderImage: "xmark.circle.fill")
testSubject(subject: subject, withExpectatations: expectedThemeElements)
}
private func testSubject(subject: Themer, withExpectatations expectations: ThemeExpectations) {
XCTAssertEqual(subject.sliderKnobColor(), expectations.expectedSliderKnobColor)
XCTAssertEqual(subject.sliderRightColor(), expectations.expectedSliderRightColor)

6
Clocker/CoreLoggerKit/Package.swift

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

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

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

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

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

8
Clocker/CoreModelKit/Package.swift

@ -6,19 +6,19 @@ import PackageDescription
let package = Package(
name: "CoreModelKit",
platforms: [
.macOS(.v10_12)
.macOS(.v10_12),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "CoreModelKit",
targets: ["CoreModelKit"]
)
),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(path: "../CoreLoggerKit/")
.package(path: "../CoreLoggerKit/"),
],
targets: [
// 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(
name: "CoreModelKitTests",
dependencies: ["CoreModelKit", "CoreLoggerKit"]
)
),
]
)

2
Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift

@ -66,7 +66,7 @@ public class TimezoneData: NSObject, NSCoding {
// Suffix
NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix,
NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds,
NSNumber(integerLiteral: 11): DateFormat.epochTime
NSNumber(integerLiteral: 11): DateFormat.epochTime,
]
public var customLabel: String?

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

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

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

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

3
Clocker/Events and Reminders/RemindersHandler.swift

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

10
Clocker/Menu Bar/StatusContainerView.swift

@ -31,10 +31,12 @@ func compactWidth(for timezone: TimezoneData) -> Int {
if timeFormat == DateFormat.twelveHour
|| timeFormat == DateFormat.twelveHourWithSeconds
|| timeFormat == DateFormat.twelveHourWithZero
|| timeFormat == DateFormat.twelveHourWithSeconds {
|| timeFormat == DateFormat.twelveHourWithSeconds
{
totalWidth += 20
} else if timeFormat == DateFormat.twentyFourHour
|| timeFormat == DateFormat.twentyFourHourWithSeconds {
|| timeFormat == DateFormat.twentyFourHourWithSeconds
{
totalWidth += 0
}
@ -75,7 +77,7 @@ class StatusContainerView: NSView {
let timeBasedAttributes = [
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
func containerWidth(for timezones: [Data]) -> CGFloat {
@ -134,7 +136,7 @@ class StatusContainerView: NSView {
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
let operation = TimezoneDataOperations(with: timezone)

3
Clocker/Menu Bar/StatusItemHandler.swift

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

8
Clocker/Menu Bar/StatusItemView.swift

@ -49,7 +49,7 @@ class StatusItemView: NSView {
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return attributes
}
@ -61,7 +61,7 @@ class StatusItemView: NSView {
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10),
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return textFontAttributes
}
@ -90,14 +90,14 @@ class StatusItemView: NSView {
locationView.leadingAnchor.constraint(equalTo: leadingAnchor),
locationView.trailingAnchor.constraint(equalTo: trailingAnchor),
locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7),
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35)
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35),
])
NSLayoutConstraint.activate([
timeView.leadingAnchor.constraint(equalTo: leadingAnchor),
timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
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() {
Timer.scheduledTimer(withTimeInterval: 5,
repeats: false) { _ in
repeats: false)
{ _ in
OperationQueue.main.addOperation {
self.setInfoLabel(CLEmptyString)
}
@ -227,7 +228,7 @@ class OnboardingSearchController: NSViewController {
"latitude": latitude,
"longitude": longitude,
"nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress
CLCustomLabel: filteredAddress,
] as [String: Any]
DataStore.shared().addTimezone(TimezoneData(with: newTimeZone))
@ -413,7 +414,7 @@ class OnboardingSearchController: NSViewController {
CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: result.placeId
CLPlaceIdentifier: result.placeId,
] as [String: Any]
return TimezoneData(with: totalPackage)

4
Clocker/Overall App/ConfigExport.swift

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

43
Clocker/Overall App/DataStore.swift

@ -18,12 +18,13 @@ enum ViewType {
case dayInMenubar
case menubarCompactMode
case dstTransitionInfo
case sync
}
class DataStore: NSObject {
private static var sharedStore = DataStore(with: UserDefaults.standard)
private var userDefaults: UserDefaults!
private var ubiquitousStore: NSUbiquitousKeyValueStore!
private var ubiquitousStore: NSUbiquitousKeyValueStore?
// Since these pref can accessed every second, let's cache this
private var shouldDisplayDayInMenubar: Bool = false
@ -41,9 +42,39 @@ class DataStore: NSObject {
init(with defaults: UserDefaults) {
super.init()
userDefaults = defaults
ubiquitousStore = NSUbiquitousKeyValueStore.default
shouldDisplayDayInMenubar = shouldDisplay(.dayInMenubar)
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] {
@ -57,7 +88,7 @@ class DataStore: NSObject {
func setTimezones(_ timezones: [Data]?) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
// iCloud sync
ubiquitousStore.set(timezones, forKey: CLDefaultPreferenceKey)
ubiquitousStore?.set(timezones, forKey: CLDefaultPreferenceKey)
}
func menubarTimezones() -> [Data]? {
@ -158,6 +189,8 @@ class DataStore: NSObject {
}
return value == 0
case .sync:
return shouldDisplayHelper(CLEnableSyncKey)
}
}
@ -170,3 +203,7 @@ class DataStore: NSObject {
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
class DateFormatterManager: NSObject {
private static var dateFormatter = DateFormatter()
private static var calendarDateFormatter = 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 CLDisplayDSTTransitionInfo = "com.abhishek.showDSTTransitionInfo"
let CLAppleInterfaceStyleKey = "AppleInterfaceStyle"
let CLEnableSyncKey = "com.abhishek.enableSync"

3
Clocker/Overall App/Themer.swift

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

2
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -283,7 +283,7 @@ extension TimezoneDataOperations {
let unableToConvertDateParameters = [
"New Date": newDate,
"Timezone": dataObject.timezone(),
"Locale": dateFormatter.locale.identifier
"Locale": dateFormatter.locale.identifier,
] as [String: Any]
Logger.log(object: unableToConvertDateParameters, for: "Date conversion failure - New Date is nil")
return CLEmptyString

10
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -73,7 +73,7 @@ class NotesPopover: NSViewController {
"1 hour before",
"2 hour before",
"1 day before",
"2 days before"
"2 days before",
]
alertPopupButton.removeAllItems()
@ -240,7 +240,7 @@ class NotesPopover: NSViewController {
let attributesDictionary = [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.paragraphStyle: style
NSAttributedString.Key.paragraphStyle: style,
]
button.attributedTitle = NSAttributedString(string: title,
@ -322,7 +322,8 @@ class NotesPopover: NSViewController {
}
private func updateTimezoneInDefaultPreferences(with override: Int,
_: OverrideType) {
_: OverrideType)
{
let timezones = DataStore.shared().timezones()
var timezoneObjects: [TimezoneData] = []
@ -391,7 +392,8 @@ class NotesPopover: NSViewController {
timezone: model.timezone(),
alertIndex: alertIndex,
reminderDate: reminderPicker.dateValue,
additionalNotes: model.note) {
additionalNotes: model.note)
{
showSuccessMessage()
}
}

2
Clocker/Panel/Notes Popover/TextViewWithPlaceholder.swift

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

16
Clocker/Panel/PanelController.swift

@ -6,18 +6,12 @@ import CoreLoggerKit
class PanelController: ParentPanelController {
@objc dynamic var hasActivePanel: Bool = false
static var sharedWindow = PanelController(windowNibName: .panel)
@IBOutlet var backgroundView: BackgroundPanelView!
override func windowDidLoad() {
super.windowDidLoad()
}
class func shared() -> PanelController {
return sharedWindow
}
override func awakeFromNib() {
super.awakeFromNib()
@ -80,6 +74,8 @@ class PanelController: ParentPanelController {
super.dismissRowActions()
updateDefaultPreferences()
setupUpcomingEventViewCollectionViewIfNeccesary()
if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false {
futureSliderView.isHidden = true
@ -154,7 +150,8 @@ class PanelController: ParentPanelController {
}
if let statusWindow = statusBackgroundWindow,
let statusButton = statusView {
let statusButton = statusView
{
var statusItemFrame = statusWindow.convertToScreen(statusButton.frame)
var statusItemScreen = NSScreen.main
var testPoint = statusItemFrame.origin
@ -221,7 +218,7 @@ class PanelController: ParentPanelController {
"Show Upcoming Event View": showUpcomingEventView == "YES" ? "Yes" : "No",
"Country": country,
"Calendar Access Provided": EventCenter.sharedCenter().calendarAccessGranted() ? "Yes" : "No",
"Number of Timezones": preferences.count
"Number of Timezones": preferences.count,
]
Logger.log(object: panelEvent, for: "openedPanel")
@ -308,6 +305,9 @@ class PanelController: ParentPanelController {
window?.orderOut(nil)
datasource = nil
upcomingEventsDataSource = nil
parentTimer?.pause()
parentTimer = nil
}
func setActivePanel(newValue: Bool) {

40
Clocker/Panel/ParentPanelController.swift

@ -172,12 +172,12 @@ class ParentPanelController: NSWindowController {
setupObservers()
updateReviewViewFontColor()
// Set the background color of the bottom buttons view to something different to indicate we're not in a release candidate
#if DEBUG
stackView.arrangedSubviews.last?.layer?.backgroundColor = NSColor(deviceRed: 255.0/255.0,
green: 150.0/255.0,
blue: 122.0/255.0,
stackView.arrangedSubviews.last?.layer?.backgroundColor = NSColor(deviceRed: 255.0 / 255.0,
green: 150.0 / 255.0,
blue: 122.0 / 255.0,
alpha: 0.5).cgColor
stackView.arrangedSubviews.last?.toolTip = "Clocker is running in Debug Mode"
#endif
@ -195,9 +195,11 @@ class ParentPanelController: NSWindowController {
selector: #selector(systemTimezoneDidChange),
name: NSNotification.Name.NSSystemTimeZoneDidChange,
object: nil)
NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification,
object: self,
queue: OperationQueue.main) { [weak self] _ in
queue: OperationQueue.main)
{ [weak self] _ in
if let sSelf = self {
sSelf.mainTableView.reloadData()
sSelf.setScrollViewConstraint()
@ -349,7 +351,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13)
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13) ?? NSFont.systemFont(ofSize: 13),
]
let leftButtonAttributedTitle = NSAttributedString(string: leftButton.title, attributes: styleAttributes)
@ -425,7 +427,8 @@ class ParentPanelController: NSWindowController {
newHeight += 20
} else if DataStore.shared().shouldDisplay(.dstTransitionInfo),
let obj = object,
TimezoneDataOperations(with: obj).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) != nil {
TimezoneDataOperations(with: obj).nextDaylightSavingsTransitionIfAvailable(with: futureSliderValue) != nil
{
newHeight += 20
}
}
@ -582,7 +585,7 @@ class ParentPanelController: NSWindowController {
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() {
let store = DataStore.shared()
@ -613,7 +616,8 @@ class ParentPanelController: NSWindowController {
if $0 < mainTableView.numberOfRows,
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 {
return
}
@ -633,7 +637,8 @@ class ParentPanelController: NSWindowController {
if let note = model.note, !note.isEmpty {
cellView.noteLabel.stringValue = note
} 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
} else {
cellView.noteLabel.stringValue = CLEmptyString
@ -733,7 +738,8 @@ class ParentPanelController: NSWindowController {
@IBAction func calendarButtonAction(_ sender: NSButton) {
if sender.title == NSLocalizedString("Click here to start.",
comment: "Button Title for no Calendar access") {
comment: "Button Title for no Calendar access")
{
showPermissionsWindow()
} else {
retrieveCalendarEvents()
@ -824,7 +830,8 @@ class ParentPanelController: NSWindowController {
if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false {
OperationQueue.main.addOperation {
if self.upcomingEventCollectionView != nil,
let upcomingEvents = eventCenter.upcomingEventsForDay(events) {
let upcomingEvents = eventCenter.upcomingEventsForDay(events)
{
self.upcomingEventsDataSource.updateEventsDataSource(upcomingEvents)
self.upcomingEventCollectionView.reloadData()
return
@ -904,7 +911,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
]
leftButton.attributedTitle = NSAttributedString(string: "Not Really", attributes: styleAttributes)
rightButton.attributedTitle = NSAttributedString(string: "Yes!", attributes: styleAttributes)
@ -940,7 +947,7 @@ class ParentPanelController: NSWindowController {
let styleAttributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
]
if leftButton.attributedTitle.string == "Not Really" {
@ -1124,7 +1131,8 @@ extension ParentPanelController: NSSharingServicePickerDelegate {
stride(from: 0, to: sortedByTime.count, by: 1).forEach {
if $0 < sortedByTime.count,
let dataModel = TimezoneData.customObject(from: sortedByTime[$0]) {
let dataModel = TimezoneData.customObject(from: sortedByTime[$0])
{
let dataOperations = TimezoneDataOperations(with: dataModel)
let date = dataOperations.todaysDate(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 = [
.mouseMoved,
.mouseEnteredAndExited,
.activeAlways
.activeAlways,
]
let clipRect = enclosingScrollView?.contentView.bounds ?? .zero

6
Clocker/Panel/UI/TimezoneCellView.swift

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

5
Clocker/Panel/Upcoming Events/UpcomingEventViewItem.swift

@ -40,7 +40,8 @@ class UpcomingEventViewItem: NSCollectionViewItem {
_ color: NSColor,
_ link: URL?,
_ delegate: UpcomingEventPanelDelegate?,
_ isCancelled: Bool) {
_ isCancelled: Bool)
{
if leadingConstraint.constant != UpcomingEventViewItem.EventLeadingConstraint / 2 {
leadingConstraint.animator().constant = UpcomingEventViewItem.EventLeadingConstraint / 2
}
@ -89,7 +90,7 @@ class UpcomingEventViewItem: NSCollectionViewItem {
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")
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,
NSColor.systemGreen,
nil)

2
Clocker/Preferences/About/PointingHandCursorButton.swift

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

5
Clocker/Preferences/App Feedback/AppFeedbackWindowController.swift

@ -80,7 +80,8 @@ class AppFeedbackWindowController: NSWindowController {
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification,
object: nil,
queue: OperationQueue.main) { _ in
queue: OperationQueue.main)
{ _ in
self.window?.backgroundColor = Themer.shared().mainBackgroundColor()
self.setup()
}
@ -201,7 +202,7 @@ class AppFeedbackWindowController: NSWindowController {
AppFeedbackConstants.CLOperatingSystemVersion: osVersion,
AppFeedbackConstants.CLClockerVersion: versionInfo,
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 appearanceTab: NSTabView!
@IBOutlet var appDisplayControl: NSSegmentedControl!
@IBOutlet var syncLabel: NSTextField!
@IBOutlet var syncSegementedControl: NSSegmentedControl!
private var themeDidChangeNotification: NSObjectProtocol?
@ -60,7 +62,7 @@ class AppearanceViewController: ParentViewController {
"4 days",
"5 days",
"6 days",
"7 days"
"7 days",
])
if #available(macOS 11.0, *) {} else {
@ -134,6 +136,10 @@ class AppearanceViewController: ParentViewController {
// True is Menubar Only and False is Menubar + Dock
let appDisplayOptions = DataStore.shared().shouldDisplay(.appDisplayOptions)
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!
@ -150,7 +156,6 @@ class AppearanceViewController: ParentViewController {
@IBOutlet var menubarModeLabel: NSTextField!
@IBOutlet var previewLabel: NSTextField!
@IBOutlet var miscelleaneousLabel: NSTextField!
@IBOutlet var dstTransitionField: NSTextField!
// Panel Preview
@IBOutlet var previewPanelTableView: NSTableView!
@ -162,6 +167,7 @@ class AppearanceViewController: ParentViewController {
showSliderLabel.stringValue = "Time Scroller".localized()
showSunriseLabel.stringValue = "Show Sunrise/Sunset".localized()
largerTextLabel.stringValue = "Larger Text".localized()
syncLabel.stringValue = "Enable iCloud Sync".localized()
futureSliderRangeLabel.stringValue = "Future Slider Range".localized()
includeDateLabel.stringValue = "Include Date".localized()
includeDayLabel.stringValue = "Include Day".localized()
@ -172,9 +178,9 @@ class AppearanceViewController: ParentViewController {
[timeFormatLabel, panelTheme,
dayDisplayOptionsLabel, showSliderLabel,
showSunriseLabel, largerTextLabel, futureSliderRangeLabel,
showSunriseLabel, largerTextLabel, syncLabel, futureSliderRangeLabel,
includeDayLabel, includeDateLabel, includePlaceLabel, appDisplayLabel, menubarModeLabel,
previewLabel, miscelleaneousLabel, dstTransitionField].forEach {
previewLabel, miscelleaneousLabel].forEach {
$0?.textColor = Themer.shared().mainTextColor()
}
@ -188,7 +194,8 @@ class AppearanceViewController: ParentViewController {
refresh(panel: true, floating: true)
if let selectedFormat = sender.selectedItem?.title,
selectedFormat.contains("ss") {
selectedFormat.contains("ss")
{
Logger.info("Selected format contains timezone format")
guard let panelController = PanelController.panel() else { return }
panelController.pauseTimer()
@ -352,8 +359,9 @@ class AppearanceViewController: ParentViewController {
previewPanelTableView.reloadData()
}
@IBAction func toggleDSTTransitionOption(_: Any) {
previewPanelTableView.reloadData()
@IBAction func toggleSync(_ sender: NSSegmentedControl) {
NSUbiquitousKeyValueStore.default.set(sender.selectedSegment == 0, forKey: CLEnableSyncKey)
DataStore.shared().setupSyncNotification()
}
}

29
Clocker/Preferences/Calendar/CalendarViewController.swift

@ -5,29 +5,26 @@ import CoreLoggerKit
import EventKit
class ClockerTextBackgroundView: NSView {
private var themeDidChangeNotification: NSObjectProtocol?
override func awakeFromNib() {
wantsLayer = true
layer?.cornerRadius = 8.0
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 {
if let themeDidChangeNotif = themeDidChangeNotification {
NotificationCenter.default.removeObserver(themeDidChangeNotif)
}
NotificationCenter.default.addObserver(self,
selector: #selector(updateBackgroundColor),
name: .themeDidChangeNotification,
object: nil)
}
override func updateLayer() {
super.updateLayer()
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
@objc func updateBackgroundColor() {
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
}
class CalendarViewController: ParentViewController {
@ -113,7 +110,7 @@ class CalendarViewController: ParentViewController {
let attributesDictionary: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.paragraphStyle: style,
NSAttributedString.Key.font: boldFont,
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor()
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
]
let attributedString = NSAttributedString(string: title,
attributes: attributesDictionary)
@ -242,13 +239,15 @@ extension CalendarViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? {
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
return message
}
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.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off
calendarCell.calendarSelected.target = self

7
Clocker/Preferences/General/PreferencesDataSource.swift

@ -104,7 +104,8 @@ extension PreferencesDataSource: NSTableViewDataSource {
var selectedDataSource: TimezoneData?
if selectedTimezones.count > row,
let model = TimezoneData.customObject(from: selectedTimezones[row]) {
let model = TimezoneData.customObject(from: selectedTimezones[row])
{
selectedDataSource = model
}
@ -161,7 +162,7 @@ extension PreferencesDataSource: NSTableViewDataSource {
if selectedTimezones.count > row {
Logger.log(object: [
"Old Label": dataObject.customLabel ?? "Error",
"New Label": formattedValue
"New Label": formattedValue,
],
for: "Custom Label Changed")
@ -174,7 +175,7 @@ extension PreferencesDataSource: NSTableViewDataSource {
Logger.log(object: [
"MethodName": "SetObjectValue",
"Selected Timezone Count": selectedTimezones.count,
"Current Row": row
"Current Row": row,
],
for: "Error in selected row count")
}

16
Clocker/Preferences/General/PreferencesViewController.swift

@ -93,9 +93,10 @@ class PreferencesViewController: ParentViewController {
name: NSNotification.Name.customLabelChanged,
object: nil)
NotificationCenter.default.addObserver(forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
NotificationCenter.default.addObserver(forName: DataStore.didSyncFromExternalSourceNotification,
object: self,
queue: OperationQueue.main) { [weak self] _ in
queue: OperationQueue.main)
{ [weak self] _ in
if let sSelf = self {
sSelf.refreshTimezoneTableView()
}
@ -238,7 +239,7 @@ class PreferencesViewController: ParentViewController {
setupColor()
startupCheckbox.integerValue = DataStore.shared().retrieve(key: CLStartAtLogin) as? Int ?? 0
searchField.bezelStyle = .roundedBezel
}
@ -250,7 +251,7 @@ class PreferencesViewController: ParentViewController {
[timezoneNameSortButton, labelSortButton, timezoneSortButton].forEach {
$0?.attributedTitle = NSAttributedString(string: $0?.title ?? CLEmptyString, attributes: [
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor(),
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!
NSAttributedString.Key.font: NSFont(name: "Avenir-Light", size: 13)!,
])
}
@ -348,7 +349,8 @@ extension PreferencesViewController: NSTableViewDataSource, NSTableViewDelegate
if let appDelegate = NSApplication.shared.delegate as? AppDelegate,
let menubarFavourites = DataStore.shared().menubarTimezones(),
menubarFavourites.isEmpty,
DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false {
DataStore.shared().shouldDisplay(.showMeetingInMenubar) == false
{
appDelegate.invalidateMenubarTimer(true)
}
@ -512,7 +514,7 @@ extension PreferencesViewController {
CLTimezoneName: formattedAddress,
CLCustomLabel: formattedAddress,
CLTimezoneID: CLEmptyString,
CLPlaceIdentifier: $0.placeId
CLPlaceIdentifier: $0.placeId,
] as [String: Any]
finalResults.append(TimezoneData(with: totalPackage))
@ -604,7 +606,7 @@ extension PreferencesViewController {
"latitude": dataObject.latitude!,
"longitude": dataObject.longitude!,
"nextUpdate": CLEmptyString,
CLCustomLabel: filteredAddress
CLCustomLabel: filteredAddress,
] as [String: Any]
// Mark if the timezone is same as local timezone

42
Clocker/Preferences/General/SearchDataSource.swift

@ -102,27 +102,27 @@ class SearchDataSource: NSObject {
timezoneArray.append(utcTimezone)
for identifier in TimeZone.knownTimeZoneIdentifiers {
guard let timezoneObject = TimeZone(identifier: identifier) else {
continue
if let timezoneObject = TimeZone(identifier: identifier) {
// Force-cast explicity since we get the identifier from `knownTimeZoneIdentifiers`
let abbreviation = timezoneObject.abbreviation()!
let identifier = timezoneObject.identifier
var tags: Set<String> = [abbreviation.lowercased(), identifier.lowercased()]
var extraTags: [String] = []
if let tagsPresent = timezoneMetadataDictionary[abbreviation] {
extraTags = tagsPresent
}
extraTags.forEach { tag in
tags.insert(tag)
}
let timezoneIdentifier = NSTimeZone(name: identifier)!
let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier,
tags: tags,
formattedName: identifier,
abbreviation: abbreviation)
timezoneArray.append(timezoneMetadata)
}
let abbreviation = timezoneObject.abbreviation() ?? "Empty"
let identifier = timezoneObject.identifier
var tags: Set<String> = [abbreviation.lowercased(), identifier.lowercased()]
var extraTags: [String] = []
if let tagsPresent = timezoneMetadataDictionary[abbreviation] {
extraTags = tagsPresent
}
extraTags.forEach { tag in
tags.insert(tag)
}
let timezoneIdentifier = NSTimeZone(name: identifier)!
let timezoneMetadata = TimezoneMetadata(timezone: timezoneIdentifier,
tags: tags,
formattedName: identifier,
abbreviation: abbreviation)
timezoneArray.append(timezoneMetadata)
}
}
@ -157,7 +157,7 @@ class SearchDataSource: NSObject {
timezoneFilteredArray = timezoneArray.filter { timezoneMetadata -> Bool in
let tags = timezoneMetadata.tags
for tag in tags where tag.contains(searchString) {
for tag in tags where tag.contains(searchString.lowercased()) {
return true
}
return false

13
Clocker/Preferences/Menu Bar/MenubarTitleProvider.swift

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

22
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -18,7 +18,7 @@ func bufferCalculatedWidth() -> Int {
if DataStore.shared().shouldShowDateInMenubar() {
totalWidth += 20
}
if DataStore.shared().shouldDisplay(.showMeetingInMenubar) {
totalWidth += 100
}
@ -37,10 +37,12 @@ func compactWidth(for timezone: TimezoneData) -> Int {
if timeFormat == DateFormat.twelveHour
|| timeFormat == DateFormat.twelveHourWithSeconds
|| timeFormat == DateFormat.twelveHourWithZero
|| timeFormat == DateFormat.twelveHourWithSeconds {
|| timeFormat == DateFormat.twelveHourWithSeconds
{
totalWidth += 20
} else if timeFormat == DateFormat.twentyFourHour
|| timeFormat == DateFormat.twentyFourHourWithSeconds {
|| timeFormat == DateFormat.twentyFourHourWithSeconds
{
totalWidth += 0
}
@ -81,7 +83,8 @@ class StatusContainerView: NSView {
if showUpcomingEventView,
let events = EventCenter.sharedCenter().eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: Date())],
events.isEmpty == false,
let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events) {
let upcomingEvent = EventCenter.sharedCenter().nextOccuring(events)
{
let calculatedWidth = bestWidth(for: upcomingEvent)
let frame = NSRect(x: previousX, y: 0, width: calculatedWidth, height: 30)
let calendarItemView = UpcomingEventStatusItemView(frame: frame)
@ -100,7 +103,7 @@ class StatusContainerView: NSView {
let timeBasedAttributes = [
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
func containerWidth(for timezones: [Data]) -> CGFloat {
@ -163,7 +166,7 @@ class StatusContainerView: NSView {
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
let operation = TimezoneDataOperations(with: timezone)
@ -173,7 +176,7 @@ class StatusContainerView: NSView {
return Int(max(bestSize.width, bestTitleSize.width) + bufferWidth)
}
private func bestWidth(for eventInfo: EventInfo) -> Int {
var textColor = hasDarkAppearance ? NSColor.white : NSColor.black
@ -185,7 +188,7 @@ class StatusContainerView: NSView {
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
let bestSize = compactModeTimeFont.size(eventInfo.metadataForMeeting(),
@ -202,7 +205,7 @@ class StatusContainerView: NSView {
if subviews.isEmpty {
assertionFailure("Subviews count should > 0")
}
for view in subviews {
if let conformingView = view as? StatusItemViewConforming {
conformingView.statusItemViewSetNeedsDisplay()
@ -236,6 +239,7 @@ class StatusContainerView: NSView {
// NSView move animation
NSAnimationContext.runAnimationGroup({ context in
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)
// The view will animate to the new origin
self.animator().frame = newFrame

30
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -22,7 +22,7 @@ class StatusItemHandler: NSObject {
return statusItem
}()
private var menubarTitleHandler = MenubarTitleProvider()
private var menubarTitleHandler = MenubarTitleProvider(with: DataStore.shared())
private var statusContainerView: StatusContainerView?
@ -115,7 +115,8 @@ class StatusItemHandler: NSObject {
userNotificationsDidChangeNotif = center.addObserver(forName: UserDefaults.didChangeNotification,
object: self,
queue: mainQueue) { _ in
queue: mainQueue)
{ _ in
self.setupStatusItem()
}
@ -202,7 +203,7 @@ class StatusItemHandler: NSObject {
let timezonesSupportingSeconds = syncedTimezones.filter { data in
if let timezoneObj = TimezoneData.customObject(from: data) {
return timezoneObj.shouldShowSeconds(DataStore.shared().timezoneFormat())
return timezoneObj.shouldShowSeconds(DataStore.shared().timezoneFormat())
}
return false
}
@ -239,22 +240,33 @@ class StatusItemHandler: NSObject {
}
func updateCompactMenubar() {
if let upcomingEvent = menubarTitleHandler.checkForUpcomingEvents() {
print("Need to construct upcoming event view \(upcomingEvent)")
if menubarTitleHandler.checkForUpcomingEvents() != nil {
// Iterate and see if we're showing the calendar item view
let upcomingEventView = retrieveUpcomingEventStatusView()
// If not, reconstruct Status Container View with another view
if upcomingEventView == nil {
constructCompactView(with: true)
}
} else {
let upcomingEventView = retrieveUpcomingEventStatusView()
upcomingEventView?.removeFromSuperview()
} else if let upcomingEventView = retrieveUpcomingEventStatusView() {
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.
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() {
if currentState == .compactText {
updateCompactMenubar()
@ -361,7 +373,7 @@ class StatusItemHandler: NSObject {
constructCompactView(with: menubarTitleHandler.checkForUpcomingEvents() != nil)
updateMenubar()
}
private func retrieveUpcomingEventStatusView() -> NSView? {
let upcomingEventView = statusContainerView?.subviews.first(where: { statusItemView in
if let upcomingEventView = statusItemView as? StatusItemViewConforming {

8
Clocker/Preferences/Menu Bar/StatusItemView.swift

@ -50,7 +50,7 @@ class StatusItemView: NSView {
NSAttributedString.Key.font: compactModeTimeFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return attributes
}
@ -62,7 +62,7 @@ class StatusItemView: NSView {
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10),
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return textFontAttributes
}
@ -91,14 +91,14 @@ class StatusItemView: NSView {
locationView.leadingAnchor.constraint(equalTo: leadingAnchor),
locationView.trailingAnchor.constraint(equalTo: trailingAnchor),
locationView.topAnchor.constraint(equalTo: topAnchor, constant: 7),
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35)
locationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.35),
])
NSLayoutConstraint.activate([
timeView.leadingAnchor.constraint(equalTo: leadingAnchor),
timeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
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.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return attributes
}
@ -33,7 +33,7 @@ class UpcomingEventStatusItemView: NSView {
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 10),
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle
NSAttributedString.Key.paragraphStyle: defaultParagraphStyle,
]
return textFontAttributes
}
@ -54,14 +54,14 @@ class UpcomingEventStatusItemView: NSView {
nextEventField.leadingAnchor.constraint(equalTo: leadingAnchor),
nextEventField.trailingAnchor.constraint(equalTo: trailingAnchor),
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([
etaField.leadingAnchor.constraint(equalTo: leadingAnchor),
etaField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
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="vjb-Ch-BZs"/>
<gridRow id="ad4-FM-AWq"/>
<gridRow id="yUG-1W-BQ2"/>
<gridRow id="fbk-ef-Req"/>
<gridRow id="B74-Mt-1h8"/>
</rows>
<columns>
<gridColumn xPlacement="trailing" id="ARE-A4-4k4"/>
@ -924,67 +924,66 @@
</connections>
</segmentedControl>
</gridCell>
<gridCell row="yUG-1W-BQ2" column="ARE-A4-4k4" id="M8P-5s-oL4">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="ZWy-WW-6H9">
<rect key="frame" x="50" y="42" width="163" height="18"/>
<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="xwt-pY-1w9">
<rect key="frame" x="9" y="43" width="204" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="159" id="NX0-Bo-SU8"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="7fj-Em-Lyh"/>
</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"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</gridCell>
<gridCell row="yUG-1W-BQ2" column="YBI-pK-gPQ" id="N12-lC-Pe1">
<segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DCB-IB-UxK">
<rect key="frame" x="229" y="39" width="81" height="24"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="PZZ-H7-LgV">
<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>
<gridCell row="fbk-ef-Req" column="YBI-pK-gPQ" id="dCk-cz-no0">
<slider key="contentView" verticalHuggingPriority="750" alphaValue="0.59999999999999998" translatesAutoresizingMaskIntoConstraints="NO" id="3cU-IS-3Qu">
<rect key="frame" x="229" y="36" width="104" height="28"/>
<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"/>
<connections>
<action selector="toggleDSTTransitionOption:" target="1aL-zR-8L4" id="9bi-NL-gMv"/>
<binding destination="Gpv-Gr-MxZ" name="selectedIndex" keyPath="values.com.abhishek.showDSTTransitionInfo" id="eG0-sL-5hE"/>
<action selector="fontSliderChanged:" target="1aL-zR-8L4" id="YAW-aA-5aR"/>
<binding destination="Gpv-Gr-MxZ" name="value" keyPath="values.userFontSize" id="Dzw-Zc-qN5"/>
</connections>
</segmentedControl>
</slider>
</gridCell>
<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="xwt-pY-1w9">
<rect key="frame" x="9" y="1" width="204" height="18"/>
<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="kkn-L8-yOj">
<rect key="frame" x="9" y="2" width="204" height="18"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="7fj-Em-Lyh"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="eD6-Fv-2tz"/>
</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"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</gridCell>
<gridCell row="fbk-ef-Req" column="YBI-pK-gPQ" id="dCk-cz-no0">
<slider key="contentView" verticalHuggingPriority="750" alphaValue="0.59999999999999998" translatesAutoresizingMaskIntoConstraints="NO" id="3cU-IS-3Qu">
<rect key="frame" x="229" y="-6" width="104" height="28"/>
<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"/>
<gridCell row="B74-Mt-1h8" column="YBI-pK-gPQ" id="kVc-I2-8Kp">
<segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncK-Q6-ag9">
<rect key="frame" x="229" y="-1" width="81" height="24"/>
<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>
<action selector="fontSliderChanged:" target="1aL-zR-8L4" id="YAW-aA-5aR"/>
<binding destination="Gpv-Gr-MxZ" name="value" keyPath="values.userFontSize" id="Dzw-Zc-qN5"/>
<action selector="toggleSync:" target="1aL-zR-8L4" id="4Mg-ie-F7U"/>
</connections>
</slider>
</segmentedControl>
</gridCell>
</gridCells>
</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">
<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"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<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"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
@ -1468,7 +1467,6 @@
<outlet property="appDisplayLabel" destination="HNj-dh-8gr" id="A6U-gp-sje"/>
<outlet property="appearanceTab" destination="SGu-yd-JQh" id="CJn-3E-Ujd"/>
<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="includeDateInMenubarControl" destination="Axn-Tb-Cdx" id="M5x-Qt-zvs"/>
<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="showSunriseLabel" destination="4lt-X6-3uU" id="tez-s5-RzL"/>
<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="timeFormat" destination="iiG-Xu-4id" id="oM3-1Y-fAF"/>
<outlet property="timeFormatLabel" destination="wtO-uL-QBf" id="udS-d6-Tep"/>
@ -1513,7 +1513,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<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">
<font key="font" size="13" name="Avenir-Light"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -1570,13 +1570,13 @@ DQ
</connections>
</button>
<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">
<rect key="frame" x="0.0" y="0.0" width="320" height="165"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="81"/>
<autoresizingMask key="autoresizingMask"/>
<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">
<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"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -1666,14 +1666,14 @@ DQ
</scroller>
</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">
<rect key="frame" x="160" y="132" width="16" height="16"/>
<rect key="frame" x="160" y="90" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="fgE-77-Vda"/>
<constraint firstAttribute="width" constant="16" id="pwe-em-e0a"/>
</constraints>
</progressIndicator>
<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>
<constraint firstAttribute="height" constant="22" id="zqt-d8-yas"/>
</constraints>
@ -1721,7 +1721,7 @@ DQ
<rect key="frame" x="0.0" y="484" width="613" height="30"/>
<subviews>
<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>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="180" id="cAs-on-f7X"/>
<constraint firstAttribute="height" constant="25" id="juv-QL-vMx"/>
@ -1736,7 +1736,7 @@ DQ
</connections>
</button>
<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>
<constraint firstAttribute="height" constant="25" id="eZL-Gr-38S"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="120" id="sJk-T7-7Lm"/>
@ -1751,7 +1751,7 @@ DQ
</connections>
</button>
<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>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="190" id="241-Rn-G6N"/>
<constraint firstAttribute="height" constant="25" id="weP-ll-vZ8"/>
@ -2010,7 +2010,7 @@ CA
<image name="ClockerIcon-512" width="1024" height="1024"/>
<image name="CurrentLocation" width="350" height="350"/>
<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="NSPreferencesGeneral" width="32" height="32"/>
<image name="Privacy" width="350" height="350"/>

6
Clocker/StartupKit/Package.swift

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

Loading…
Cancel
Save