Browse Source

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

pull/101/head
Abhishek 4 years ago
parent
commit
c2f7753dc3
  1. 18
      Clocker/Clocker.xcodeproj/project.pbxproj
  2. 2
      Clocker/Clocker/ca.lproj/Localizable.strings
  3. 2
      Clocker/Clocker/de.lproj/Localizable.strings
  4. 1
      Clocker/Clocker/en.lproj/Localizable.strings
  5. 10
      Clocker/Clocker/en.lproj/Panel.xib
  6. 2
      Clocker/Clocker/es.lproj/Localizable.strings
  7. 2
      Clocker/Clocker/fr.lproj/Localizable.strings
  8. 2
      Clocker/Clocker/hi.lproj/Localizable.strings
  9. 2
      Clocker/Clocker/it.lproj/Localizable.strings
  10. 2
      Clocker/Clocker/ja.lproj/Localizable.strings
  11. 2
      Clocker/Clocker/ko.lproj/Localizable.strings
  12. 2
      Clocker/Clocker/nl.lproj/Localizable.strings
  13. 1
      Clocker/Clocker/pt-BR.lproj/Localizable.strings
  14. 2
      Clocker/Clocker/ru.lproj/Localizable.strings
  15. 2
      Clocker/Clocker/zh-Hans.lproj/Localizable.strings
  16. 1
      Clocker/Clocker/zh-Hant.lproj/Localizable.strings
  17. 2
      Clocker/ClockerUITests/AboutUsTests.swift
  18. 53
      Clocker/ClockerUITests/CopyToClipboardTests.swift
  19. 4
      Clocker/ClockerUITests/FloatingWindowTests.swift
  20. 2
      Clocker/ClockerUITests/OnboardingSearchTests.swift
  21. 8
      Clocker/ClockerUITests/PreferencesTest.swift
  22. 15
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  23. 28
      Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift
  24. 81
      Clocker/Events and Reminders/CalendarHandler.swift
  25. 11
      Clocker/Events and Reminders/EventCenter.swift
  26. 9
      Clocker/Overall App/Themer.swift
  27. 8
      Clocker/Panel/Data Layer/TimezoneDataOperations.swift
  28. 3
      Clocker/Panel/Notes Popover/NotesPopover.swift
  29. 2
      Clocker/Panel/ParentPanelController+ModernSlider.swift
  30. 45
      Clocker/Panel/ParentPanelController.swift
  31. 46
      Clocker/Panel/Rate Controller/UpcomingEventView.swift
  32. 3
      Clocker/Panel/UI/HourMarkerViewItem.xib
  33. 4
      Clocker/Panel/UI/TimeMarkerViewItem.swift
  34. 8
      Clocker/Panel/UI/TimezoneCellView.swift
  35. 5
      Clocker/Panel/UI/TimezoneDataSource.swift
  36. 164
      Clocker/Panel/UI/Toasty.swift
  37. 4
      Clocker/Preferences/About/AboutViewController.swift
  38. 6
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  39. 1
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift

18
Clocker/Clocker.xcodeproj/project.pbxproj

@ -54,7 +54,7 @@
3531F82126938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; };
3531F82226938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; };
3531F82326938D7700DF0111 /* GoogleDataTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3531F80526938D7700DF0111 /* GoogleDataTransport.framework */; };
357391872507277500D30819 /* HourMarkerViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357391852507277500D30819 /* HourMarkerViewItem.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 */; };
3595FAD0227F88BC0044A12A /* UserDefaults + KVOExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */; };
@ -127,6 +127,7 @@
35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */; };
35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */; };
35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */; };
35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E65124268EDD2E00E3E1E3 /* Toasty.swift */; };
9A0A1C8C20903DBD0012003B /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9E87651C1FEDC500A7A2DF /* CoreLocation.framework */; };
9A13BAD61CA87F08007C6CBE /* Panel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAD81CA87F08007C6CBE /* Panel.xib */; };
9A13BAE01CA882FA007C6CBE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */; };
@ -284,7 +285,7 @@
3531F80426938D7700DF0111 /* leveldb-library.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "leveldb-library.framework"; path = "Frameworks/Firebase/leveldb-library.framework"; sourceTree = "<group>"; };
3531F80526938D7700DF0111 /* GoogleDataTransport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleDataTransport.framework; path = Frameworks/Firebase/GoogleDataTransport.framework; sourceTree = "<group>"; };
3569A44E25441F320087E254 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
357391852507277500D30819 /* HourMarkerViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourMarkerViewItem.swift; sourceTree = "<group>"; };
357391852507277500D30819 /* TimeMarkerViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeMarkerViewItem.swift; sourceTree = "<group>"; };
357391862507277500D30819 /* HourMarkerViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HourMarkerViewItem.xib; sourceTree = "<group>"; };
3579765D2680208C009DDA6E /* ParentPanelController+ModernSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParentPanelController+ModernSlider.swift"; sourceTree = "<group>"; };
3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults + KVOExtensions.swift"; sourceTree = "<group>"; };
@ -358,6 +359,7 @@
35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventCenter.swift; sourceTree = "<group>"; };
35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemindersHandler.swift; sourceTree = "<group>"; };
35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = SOURCE_ROOT; };
35E65124268EDD2E00E3E1E3 /* Toasty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toasty.swift; sourceTree = "<group>"; };
9A13BAD71CA87F08007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/Panel.xib; sourceTree = "<group>"; };
9A13BAE11CA882FA007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
9A13BAEB1CA88A76007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -676,8 +678,9 @@
35C36F552259DD8A002FA5C6 /* TimezoneCellView.swift */,
35C36F532259DD8A002FA5C6 /* TimezoneDataSource.swift */,
35C36F322259D7C3002FA5C6 /* AddTableViewCell.swift */,
357391852507277500D30819 /* HourMarkerViewItem.swift */,
357391852507277500D30819 /* TimeMarkerViewItem.swift */,
357391862507277500D30819 /* HourMarkerViewItem.xib */,
35E65124268EDD2E00E3E1E3 /* Toasty.swift */,
);
path = UI;
sourceTree = "<group>";
@ -1240,7 +1243,7 @@
};
C2A632A020EAC5EE00EB6BEA /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputPaths = (
@ -1248,9 +1251,9 @@
name = SwiftFormat;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "if which swiftformat >/dev/null; then\n swiftformat . --swiftversion 5\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n";
shellScript = "if which swiftformat >/dev/null; then\\n swiftformat . --swiftversion 5\\nelse\\n echo \\\"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\\\"\\nfi\\n\n";
};
/* End PBXShellScriptBuildPhase section */
@ -1309,7 +1312,7 @@
35C36EF722595F14002FA5C6 /* FinalOnboardingViewController.swift in Sources */,
35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */,
9A5951BD1C1D0A8D009C17AA /* CommonStrings.m in Sources */,
357391872507277500D30819 /* HourMarkerViewItem.swift in Sources */,
357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */,
35C36F782259E1D0002FA5C6 /* Foundation + Additions.swift in Sources */,
35C36F16225961DA002FA5C6 /* Date+Inits.swift in Sources */,
35C36F4F2259D981002FA5C6 /* AppDefaults.swift in Sources */,
@ -1346,6 +1349,7 @@
35C36F462259D892002FA5C6 /* DataStore.swift in Sources */,
9ACF618D231DABAE00F5E51E /* SearchDataSource.swift in Sources */,
3508CC9F259A000E000E3530 /* StatusItemHandler.swift in Sources */,
35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */,
C2CCCD8220619C4C00F2DFC2 /* LocationController.swift in Sources */,
35C36F4B2259D971002FA5C6 /* UnderlinedButton.swift in Sources */,
9AB6F1562259CF3900A44663 /* CalendarViewController.swift in Sources */,

2
Clocker/Clocker/ca.lproj/Localizable.strings

@ -159,3 +159,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/de.lproj/Localizable.strings

@ -159,3 +159,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

1
Clocker/Clocker/en.lproj/Localizable.strings

@ -162,3 +162,4 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

10
Clocker/Clocker/en.lproj/Panel.xib

@ -41,7 +41,7 @@
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" ignoresCycle="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="1000" y="379" width="350" height="460"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1025"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" focusRingType="none" misplaced="YES" id="6" customClass="BackgroundPanelView" customModule="Clocker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="350" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -313,9 +313,9 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<scrollView wantsLayer="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasVerticalScroller="NO" horizontalScrollElasticity="allowed" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="htc-pO-AqH">
<rect key="frame" x="0.0" y="0.0" width="350" height="40"/>
<clipView key="contentView" drawsBackground="NO" id="N1e-zE-F86">
<rect key="frame" x="0.0" y="0.0" width="350" height="40"/>
<rect key="frame" x="0.0" y="0.0" width="350" height="50"/>
<clipView key="contentView" drawsBackground="NO" id="N1e-zE-F86" customClass="DraggableClipView" customModule="Clocker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="350" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView focusRingType="none" allowsEmptySelection="NO" id="lxA-64-3QU">
@ -333,7 +333,7 @@
</subviews>
<color key="backgroundColor" name="systemIndigoColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" toolTip="Hello Sonali." wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" horizontal="YES" id="8II-oP-xbd" customClass="ThinScroller" customModule="Clocker" customModuleProvider="target">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" horizontal="YES" id="8II-oP-xbd" customClass="ThinScroller" customModule="Clocker" customModuleProvider="target">
<rect key="frame" x="-100" y="-100" width="340" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>

2
Clocker/Clocker/es.lproj/Localizable.strings

@ -161,3 +161,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/fr.lproj/Localizable.strings

@ -160,3 +160,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/hi.lproj/Localizable.strings

@ -155,3 +155,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/it.lproj/Localizable.strings

@ -160,3 +160,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/ja.lproj/Localizable.strings

@ -159,3 +159,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/ko.lproj/Localizable.strings

@ -162,3 +162,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/nl.lproj/Localizable.strings

@ -159,3 +159,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

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

@ -161,3 +161,4 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/Clocker/ru.lproj/Localizable.strings

@ -152,3 +152,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

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

@ -153,3 +153,5 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

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

@ -161,3 +161,4 @@
// DST changes
"Daylights Saving transition will occur in < 24 hours" = "Daylights Saving transition will occur in < 24 hours";
"Copied to Clipboard" = "Copied to Clipboard";

2
Clocker/ClockerUITests/AboutUsTests.swift

@ -33,7 +33,7 @@ class AboutUsTests: XCTestCase {
tapAboutTab()
let appDisplayName = "CFBundleDisplayName".localizedString()
let expectedVersion = "\(appDisplayName) 21.02.03 (92)"
let expectedVersion = "\(appDisplayName) 21.07.01 (93)"
guard let presentVersion = app.windows["Clocker"].staticTexts["ClockerVersion"].value as? String else {
XCTFail("Present version not present")

53
Clocker/ClockerUITests/CopyToClipboardTests.swift

@ -0,0 +1,53 @@
// Copyright © 2015 Abhishek Banthia
import XCTest
class CopyToClipboardTests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
if app.tables["FloatingTableView"].exists == false {
app.tapMenubarIcon()
app.buttons["FloatingPin"].click()
}
}
override func tearDownWithError() throws {
app = nil
}
func testFullCopy() throws {
let cellCount = app.tables["FloatingTableView"].cells.count
var clipboardValue = String()
for cellIndex in 0..<cellCount {
let cell = app.tables["FloatingTableView"].cells.element(boundBy: cellIndex)
let customLabel = cell.staticTexts["CustomNameLabelForCell"].value ?? "Nil Custom Label"
let time = cell.staticTexts["ActualTime"].value ?? "Nil Value"
clipboardValue.append(contentsOf: "\(customLabel) - \(time)\n")
}
app.buttons["Share"].click()
app/*@START_MENU_TOKEN@*/.menuItems["Copy All Times"]/*[[".dialogs[\"Clocker Panel\"]",".buttons[\"Share\"]",".menus.menuItems[\"Copy All Times\"]",".menuItems[\"Copy All Times\"]"],[[[-1,3],[-1,2],[-1,1,2],[-1,0,1]],[[-1,3],[-1,2],[-1,1,2]],[[-1,3],[-1,2]]],[0]]@END_MENU_TOKEN@*/.click()
let clipboard = NSPasteboard.general.string(forType: .string)
XCTAssert(clipboardValue == clipboard)
}
func testIndividualTimezoneCopy() {
let cell = app.tables["FloatingTableView"].cells.firstMatch
let customLabel = cell.staticTexts["CustomNameLabelForCell"].value ?? "Nil Custom Label"
let time = cell.staticTexts["ActualTime"].value ?? "Nil Value"
let expectedValue = "\(customLabel) - \(time)"
// Tap to copy!
cell.tap()
let clipboard = NSPasteboard.general.string(forType: .string) ?? "Empty Pasteboard"
XCTAssert(expectedValue == clipboard, "Clipboard value (\(clipboard)) doesn't match expected result")
}
}

4
Clocker/ClockerUITests/FloatingWindowTests.swift

@ -112,9 +112,9 @@ class FloatingWindowTests: XCTestCase {
miscTab.click()
if floatingSlider {
app.radioGroups["FutureSlider"].radioButtons["No"].click()
app.radioGroups["FutureSlider"].radioButtons["Hide"].click()
} else {
app.radioGroups["FutureSlider"].radioButtons["Yes"].click()
app.radioGroups["FutureSlider"].radioButtons["Legacy"].click()
}
let newFloatingSliderExists = app.sliders["FloatingSlider"].exists

2
Clocker/ClockerUITests/OnboardingSearchTests.swift

@ -79,7 +79,7 @@ class OnboardingSearchTests: XCTestCase {
func testMispelledCityNameSearch() throws {
let searchField = app.searchFields["MainSearchField"]
searchField.reset(text: "ajsdkjasdkjhasdkashkjda")
searchField.reset(text: "ajsdkjasdkjhasdkashkjdazasdasdas")
searchField.typeKey(XCUIKeyboardKey.return, modifierFlags: XCUIElement.KeyModifierFlags())
sleep(2) // Wait for the query to return

8
Clocker/ClockerUITests/PreferencesTest.swift

@ -31,7 +31,7 @@ class PreferencesTest: XCTestCase {
return
}
app.windows["Clocker"].tables["TimezoneTableView"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)).click()
app.windows["Clocker"].tables["TimezoneTableView"].tableRows.firstMatch.click()
XCTAssertTrue(app.checkBoxes["DeleteTimezone"].isEnabled)
}
@ -334,7 +334,7 @@ class PreferencesTest: XCTestCase {
if rowQueryCount > 0 {
// Table Rows aren't hittable in Xcode 12.0 (10/7/20) and so we need to find a closer co-ordinate and perform click()
let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let currentElement = clockerWindow.tables["TimezoneTableView"].tableRows.firstMatch
currentElement.click()
for _ in 0 ..< rowQueryCount {
@ -416,7 +416,7 @@ extension XCTestCase {
return
}
let currentElement = app.windows["Clocker"].tableRows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let currentElement = app.windows["Clocker"].tableRows.firstMatch
currentElement.click()
while rowQueryCount > 0 {
@ -442,7 +442,7 @@ extension XCTestCase {
}
private func deleteAtRow(_ rowToDelete: XCUIElement, for _: XCUIApplication, shouldSleep: Bool) {
rowToDelete.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)).click()
rowToDelete.click()
rowToDelete.typeKey(XCUIKeyboardKey.delete, modifierFlags: XCUIElement.KeyModifierFlags())
if shouldSleep {
sleep(2)

15
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -115,7 +115,20 @@ class ClockerUnitTests: XCTestCase {
XCTFail("Default preferences aren't in the correct format")
return
}
let oldCount = currentFavourites.count
// Check if timezone with test identifier is present.
let filteredCount = currentFavourites.filter {
let timezone = TimezoneData.customObject(from: $0)
return timezone?.placeID == "TestIdentifier"
}
// California is absent. Add it!
if filteredCount.count == 0 {
let timezoneData = TimezoneData(with: california)
let operationsObject = TimezoneDataOperations(with: timezoneData)
operationsObject.saveObject()
}
let oldCount = (defaults.object(forKey: CLDefaultPreferenceKey) as? [Data])?.count ?? 0
currentFavourites = currentFavourites.filter {
let timezone = TimezoneData.customObject(from: $0)

28
Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift

@ -20,6 +20,7 @@ public struct DateFormat {
public static let twelveHourWithZeroSeconds = "hh:mm:ss a"
public static let twelveHourWithoutSuffix = "hh:mm"
public static let twelveHourWithoutSuffixAndSeconds = "hh:mm:ss"
public static let epochTime = "epoch"
}
// Non-class type cannot conform to NSCoding!
@ -44,6 +45,7 @@ public class TimezoneData: NSObject, NSCoding {
case twelveHourPrecedingZeroSeconds = 8
case twelveHourWithoutSuffix = 10
case twelveHourWithoutSuffixAndSeconds = 11
case epochTime = 12
}
static let values = [
@ -61,6 +63,7 @@ public class TimezoneData: NSObject, NSCoding {
// Suffix
NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix,
NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds,
NSNumber(integerLiteral: 11): DateFormat.epochTime,
]
public var customLabel: String?
@ -266,6 +269,8 @@ public class TimezoneData: NSObject, NSCoding {
overrideFormat = .twelveHourWithoutSuffix
} else if shouldOverride == 11 {
overrideFormat = .twelveHourWithoutSuffixAndSeconds
} else if shouldOverride == 12 {
overrideFormat = .epochTime
} else {
assertionFailure("Chosen a wrong timezone format")
}
@ -299,27 +304,28 @@ public class TimezoneData: NSObject, NSCoding {
let chosenDefault = currentFormat
let timeFormat = TimezoneData.values[chosenDefault] ?? DateFormat.twelveHour
if overrideFormat == .globalFormat {
switch overrideFormat {
case .globalFormat:
return timeFormat
} else if overrideFormat == .twelveHourFormat {
case .twelveHourFormat:
return DateFormat.twelveHour
} else if overrideFormat == .twentyFourFormat {
case .twentyFourFormat:
return DateFormat.twentyFourHour
} else if overrideFormat == .twelveHourWithSeconds {
case .twelveHourWithSeconds:
return DateFormat.twelveHourWithSeconds
} else if overrideFormat == .twentyHourWithSeconds {
case .twentyHourWithSeconds:
return DateFormat.twentyFourHourWithSeconds
} else if overrideFormat == .twelveHourPrecedingZero {
case .twelveHourPrecedingZero:
return DateFormat.twelveHourWithZero
} else if overrideFormat == .twelveHourPrecedingZeroSeconds {
case .twelveHourPrecedingZeroSeconds:
return DateFormat.twelveHourWithZeroSeconds
} else if overrideFormat == .twelveHourWithoutSuffix {
case .twelveHourWithoutSuffix:
return DateFormat.twelveHourWithoutSuffix
} else if overrideFormat == .twelveHourWithoutSuffixAndSeconds {
case .twelveHourWithoutSuffixAndSeconds:
return DateFormat.twelveHourWithoutSuffixAndSeconds
case .epochTime:
return DateFormat.epochTime
}
return timeFormat
}
public func shouldShowSeconds(_ currentFormat: NSNumber) -> Bool {

81
Clocker/Events and Reminders/CalendarHandler.swift

@ -150,7 +150,7 @@ extension EventCenter {
return menubarText
}
func nextOccuring(_: [EventInfo]) -> EKEvent? {
func nextOccuring(_: [EventInfo]) -> EventInfo? {
if calendarAccessDenied() || calendarAccessNotDetermined() {
return nil
}
@ -162,14 +162,14 @@ extension EventCenter {
}.first
if let firstEvent = filteredEvent {
return firstEvent.event
return firstEvent
}
let filteredAllDayEvent = relevantEvents.filter {
$0.isAllDay
}.first
return filteredAllDayEvent?.event
return filteredAllDayEvent
}
func initializeStoreIfNeccesary() {
@ -201,7 +201,7 @@ extension EventCenter {
for event in events {
if selectedCalendars.contains(event.event.calendar.calendarIdentifier) {
if filteredEvents[date] == nil {
filteredEvents[date] = []
filteredEvents[date] = Array()
}
filteredEvents[date]?.append(event)
@ -332,14 +332,82 @@ extension EventCenter {
let isEndDate = autoupdatingCalendar.isDate(date, inSameDayAs: event.endDate) && (event.startDate.compare(date) == .orderedAscending)
let isAllDay = event.isAllDay || (event.startDate.compare(date) == .orderedAscending && event.endDate.compare(nextDate) == .orderedSame)
let isSingleDay = event.isAllDay && (event.startDate.compare(date) == .orderedSame && event.endDate.compare(nextDate) == .orderedSame)
let meetingURL = retrieveMeetingURL(event)
let eventInfo = EventInfo(event: event,
isStartDate: isStartDate,
isEndDate: isEndDate,
isAllDay: isAllDay,
isSingleDay: isSingleDay)
isSingleDay: isSingleDay,
meetingURL: meetingURL)
return eventInfo
}
static var dataDetector: NSDataDetector?
// Borrowing logic from Ityscal
@discardableResult
private func findAppropriateURLs(_ description: String) -> URL? {
guard let results = EventCenter.dataDetector?.matches(in: description, options: .reportCompletion, range: NSMakeRange(0, description.count)) else {
return nil
}
for result in results {
if result.resultType == .link, var actualLink = result.url?.absoluteString {
// Check for Zoom links
if actualLink.contains("zoom.us/j/") || actualLink.contains("zoom.us/s/") || actualLink.contains("zoom.us/w/") {
// Create a Zoom App link
let workspace = NSWorkspace.shared
if workspace.urlForApplication(toOpen: URL(string: "zoommtg://")!) != nil {
actualLink = actualLink.replacingOccurrences(of: "https://", with: "zoommtg://")
actualLink = actualLink.replacingOccurrences(of: "?", with: "&")
actualLink = actualLink.replacingOccurrences(of: "/j/", with: "/join?confno=")
actualLink = actualLink.replacingOccurrences(of: "/s/", with: "/join?confno=")
actualLink = actualLink.replacingOccurrences(of: "/w/", with: "/join?confno=")
if let appLink = URL(string: actualLink) {
return appLink
}
}
} else if actualLink.contains("zoommtg://")
|| actualLink.contains("meet.google.com/")
|| actualLink.contains("hangouts.google.com/")
|| actualLink.contains("webex.com/")
|| actualLink.contains("gotomeeting.com/join")
|| actualLink.contains("ringcentral.com/j")
|| actualLink.contains("bigbluebutton.org/gl")
|| actualLink.contains("://bigbluebutton.")
|| actualLink.contains("://bbb.")
|| actualLink.contains("indigo.collocall.de")
|| actualLink.contains("public.senfcall.de")
|| actualLink.contains("youcanbook.me/zoom/")
|| actualLink.contains("workplace.com/groupcall") {
if let zoomLink = result.url {
return zoomLink
}
}
}
}
return nil
}
private func retrieveMeetingURL(_ event: EKEvent) -> URL? {
if EventCenter.dataDetector == nil {
// TODO: Handle Try-Catch gracefully
EventCenter.dataDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
}
if let location = event.location {
return findAppropriateURLs(location)
}
if let url = event.url {
return findAppropriateURLs(url.absoluteString)
}
if let notes = event.notes {
return findAppropriateURLs(notes)
}
return nil
}
}
struct CalendarInfo {
@ -353,4 +421,5 @@ struct EventInfo {
let isEndDate: Bool
let isAllDay: Bool
let isSingleDay: Bool
let meetingURL: URL?
}

11
Clocker/Events and Reminders/EventCenter.swift

@ -17,6 +17,8 @@ class EventCenter: NSObject {
var filteredEvents: [Date: [EventInfo]] = [:]
private let fetchQueue = DispatchQueue(label: "com.abhishek.fetch")
@discardableResult class func sharedCenter() -> EventCenter {
return shared
}
@ -41,11 +43,16 @@ class EventCenter: NSObject {
private func refetchAll() {
Logger.info("\nRefetching events from the store")
eventsForDate = [:]
filteredEvents = [:]
autoreleasepool {
fetchQueue.async {
// We get events for a 120 day period.
// If the user uses a calendar often, this will be called frequently
fetchEvents(-40, 80)
self.fetchEvents(-40, 80)
}
}
}
}

9
Clocker/Overall App/Themer.swift

@ -442,6 +442,15 @@ extension Themer {
NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0)
}
func videoCallImage() -> NSImage? {
if #available(macOS 11.0, *) {
let symbolConfig = NSImage.SymbolConfiguration(pointSize: 20, weight: .regular)
return symbolImage(for: "video.circle.fill")?.withSymbolConfiguration(symbolConfig)
} else {
return nil
}
}
func symbolImage(for name: String) -> NSImage? {
assert(name.isEmpty == false)

8
Clocker/Panel/Data Layer/TimezoneDataOperations.swift

@ -27,11 +27,19 @@ extension TimezoneDataOperations {
return CLEmptyString
}
if (dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == DateFormat.epochTime) {
let timezone = TimeZone(identifier: dataObject.timezone())
let offset = timezone?.secondsFromGMT(for: newDate) ?? 0
let value = Int(Date().timeIntervalSince1970 + Double(offset))
return "\(value)"
}
let dateFormatter = DateFormatterManager.dateFormatterWithFormat(with: .none,
format: dataObject.timezoneFormat(DataStore.shared().timezoneFormat()),
timezoneIdentifier: dataObject.timezone(),
locale: Locale.autoupdatingCurrent)
return dateFormatter.string(from: newDate)
}

3
Clocker/Panel/Notes Popover/NotesPopover.swift

@ -92,7 +92,8 @@ class NotesPopover: NSViewController {
"hh:mm:ss a (07:08:09 PM)",
"-- 12 Hour w/o AM/PM --",
"hh:mm (07:08)",
"hh:mm:ss (07:08:09)"]
"hh:mm:ss (07:08:09)",
"Epoch Time"]
timeFormatControl.removeAllItems()
timeFormatControl.addItems(withTitles: supportedTimeFormats)

2
Clocker/Panel/ParentPanelController+ModernSlider.swift

@ -9,7 +9,7 @@ extension ParentPanelController: NSCollectionViewDataSource {
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: HourMarkerViewItem.reuseIdentifier, for: indexPath) as! HourMarkerViewItem
let item = collectionView.makeItem(withIdentifier: TimeMarkerViewItem.reuseIdentifier, for: indexPath) as! TimeMarkerViewItem
item.setup(with: indexPath.item)
return item
}

45
Clocker/Panel/ParentPanelController.swift

@ -184,7 +184,9 @@ class ParentPanelController: NSWindowController {
if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false {
futureSliderView.isHidden = true
if modernContainerView != nil {
modernContainerView.isHidden = true
}
} else if let value = DataStore.shared().retrieve(key: CLDisplayFutureSliderKey) as? NSNumber {
if value.intValue == 1 {
futureSliderView.isHidden = false
@ -612,6 +614,9 @@ class ParentPanelController: NSWindowController {
cellView.relativeDate.stringValue = dataOperation.date(with: futureSliderValue, displayType: .panel)
cellView.currentLocationIndicator.isHidden = !model.isSystemTimezone
cellView.sunriseImage.image = model.isSunriseOrSunset ? Themer.shared().sunriseImage() : Themer.shared().sunsetImage()
if #available(macOS 10.14, *) {
cellView.sunriseImage.contentTintColor = model.isSunriseOrSunset ? NSColor.systemYellow : NSColor.systemOrange
}
if let note = model.note, !note.isEmpty {
cellView.noteLabel.stringValue = note
} else if DataStore.shared().shouldDisplay(.dstTransitionInfo),
@ -696,6 +701,14 @@ class ParentPanelController: NSWindowController {
func removeUpcomingEventView() {
OperationQueue.main.addOperation {
let eventCenter = EventCenter.sharedCenter()
let now = Date()
if let events = eventCenter.eventsForDate[NSCalendar.autoupdatingCurrent.startOfDay(for: now)], events.isEmpty == false {
if let upcomingEvent = eventCenter.nextOccuring(events), let meetingLink = upcomingEvent.meetingURL {
NSWorkspace.shared.open(meetingLink)
}
}
if self.stackView.arrangedSubviews.contains(self.upcomingEventView!), self.upcomingEventView?.isHidden == false {
self.upcomingEventView?.isHidden = true
UserDefaults.standard.set("NO", forKey: CLShowUpcomingEventView)
@ -810,9 +823,9 @@ class ParentPanelController: NSWindowController {
return
}
self.calendarColorView.layer?.backgroundColor = upcomingEvent.calendar.color.cgColor
self.nextEventLabel.stringValue = upcomingEvent.title
self.nextEventLabel.toolTip = upcomingEvent.title
self.calendarColorView.layer?.backgroundColor = upcomingEvent.event.calendar.color.cgColor
self.nextEventLabel.stringValue = upcomingEvent.event.title
self.nextEventLabel.toolTip = upcomingEvent.event.title
if upcomingEvent.isAllDay == true {
let title = events.count == 1 ? "All-Day" : "All Day - Total \(events.count) events today"
self.setCalendarButtonTitle(buttonTitle: title)
@ -822,12 +835,16 @@ class ParentPanelController: NSWindowController {
return
}
let timeSince = Date().timeAgo(since: upcomingEvent.startDate)
let timeSince = Date().timeAgo(since: upcomingEvent.event.startDate)
let withoutAn = timeSince.replacingOccurrences(of: "an", with: CLEmptyString)
let withoutAgo = withoutAn.replacingOccurrences(of: "ago", with: CLEmptyString)
self.setCalendarButtonTitle(buttonTitle: "in \(withoutAgo.lowercased())")
if upcomingEvent.meetingURL != nil {
self.whiteRemoveButton.image = Themer.shared().videoCallImage()
}
if #available(OSX 10.14, *) {
PerfLogger.endMarker("Fetch Calendar Events")
}
@ -1077,9 +1094,27 @@ extension ParentPanelController: NSSharingServicePickerDelegate {
}
func sharingServicePicker(_: NSSharingServicePicker, sharingServicesForItems _: [Any], proposedSharingServices proposed: [NSSharingService]) -> [NSSharingService] {
let copySharingService = NSSharingService(title: "Copy All Times", image: NSImage(), alternateImage: nil) {
let timezones = DataStore.shared().timezones()
var clipboardCopy = String()
for encodedTimezone in timezones {
if let timezoneObject = TimezoneData.customObject(from: encodedTimezone) {
let operations = TimezoneDataOperations(with: timezoneObject)
clipboardCopy.append("\(timezoneObject.formattedTimezoneLabel()) - \(operations.time(with: 0))\n")
}
}
let pasteboard = NSPasteboard.general
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(clipboardCopy, forType: .string)
}
let allowedServices: Set<String> = Set(["Messages", "Notes"])
return proposed.filter { service in
let filteredServices = proposed.filter { service in
allowedServices.contains(service.title)
}
var newProposedServices: [NSSharingService] = [copySharingService]
newProposedServices.append(contentsOf: filteredServices)
return newProposedServices
}
}

46
Clocker/Panel/Rate Controller/UpcomingEventView.swift

@ -88,53 +88,33 @@ class ThinScroller: NSScroller {
class DraggableClipView: NSClipView {
private var clickPoint: NSPoint!
private var originalOrigin: NSPoint!
private var trackingArea: NSTrackingArea?
override func mouseDown(with event: NSEvent) {
print("2")
super.mouseDown(with: event)
clickPoint = event.locationInWindow
originalOrigin = bounds.origin
print("Click point \(clickPoint!)")
let keepOn = true
while keepOn {
let newEvent = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp])
var gestureInProgress = true
while gestureInProgress {
let newEvent = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp, .leftMouseDown])
switch newEvent?.type {
case .leftMouseDragged:
print("Hello 1")
let scale = (superview as? NSScrollView)?.magnification ?? 1.0
let newPoint = event.locationInWindow
let newOrigin = NSPoint(x: originalOrigin.x + (clickPoint.x - newPoint.x) / scale,
y: originalOrigin.y + (clickPoint.y - newPoint.y) / scale)
let newPoint = newEvent?.locationInWindow
let xCoOrdinate = clickPoint.x - (newPoint?.x ?? 0)
let newOrigin = NSPoint(x: bounds.origin.x + xCoOrdinate,
y: 0)
let constrainedRect = constrainBoundsRect(NSRect(origin: newOrigin, size: bounds.size))
scroll(to: constrainedRect.origin)
superview?.reflectScrolledClipView(self)
return
case .leftMouseDown:
clickPoint = event.locationInWindow
case .leftMouseUp:
clickPoint = nil
gestureInProgress = false
default:
print("Hello2")
}
print("Default case is happening \(event.type)")
}
}
override func mouseDragged(with event: NSEvent) {
print("1")
super.mouseDragged(with: event)
// Account for a magnified parent scrollview.
let scale = (superview as? NSScrollView)?.magnification ?? 1.0
let newPoint = event.locationInWindow
let newOrigin = NSPoint(x: originalOrigin.x + (clickPoint.x - newPoint.x) / scale,
y: originalOrigin.y + (clickPoint.y - newPoint.y) / scale)
let constrainedRect = constrainBoundsRect(NSRect(origin: newOrigin, size: bounds.size))
scroll(to: constrainedRect.origin)
superview?.reflectScrolledClipView(self)
}
override func mouseUp(with event: NSEvent) {
print("3")
super.mouseUp(with: event)
clickPoint = nil
}
override func updateTrackingAreas() {

3
Clocker/Panel/UI/HourMarkerViewItem.xib

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -27,7 +26,7 @@
</constraints>
<point key="canvasLocation" x="-17.5" y="283"/>
</customView>
<customObject id="p0M-E4-898" customClass="HourMarkerViewItem" customModule="Clocker" customModuleProvider="target">
<customObject id="p0M-E4-898" customClass="TimeMarkerViewItem" customModule="Clocker" customModuleProvider="target">
<connections>
<outlet property="verticalLineView" destination="9oV-Ab-UG7" id="0d5-Lu-gh3"/>
<outlet property="view" destination="Hz6-mo-xeY" id="9S0-wg-csq"/>

4
Clocker/Panel/UI/HourMarkerViewItem.swift → Clocker/Panel/UI/TimeMarkerViewItem.swift

@ -2,13 +2,13 @@
import Cocoa
class HourMarkerViewItem: NSCollectionViewItem {
class TimeMarkerViewItem: NSCollectionViewItem {
static let topConstraintIdentifier = "constrainFromTop"
static let reuseIdentifier = NSUserInterfaceItemIdentifier("HourMarkerViewItem")
@IBOutlet var verticalLineView: NSView!
func setup(with index: Int) {
for constraint in view.constraints where constraint.identifier == HourMarkerViewItem.topConstraintIdentifier {
for constraint in view.constraints where constraint.identifier == TimeMarkerViewItem.topConstraintIdentifier {
constraint.constant = index % 4 == 0 ? 0 : 20
}
verticalLineView.wantsLayer = true

8
Clocker/Panel/UI/TimezoneCellView.swift

@ -198,6 +198,14 @@ class TimezoneCellView: NSTableCellView {
}
override func mouseDown(with _: NSEvent) {
// Text is copied in the following format: Chicago - 1625185925
let clipboardCopy = "\(customName.stringValue) - \(time.stringValue)"
let pasteboard = NSPasteboard.general
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(clipboardCopy, forType: .string)
self.window?.contentView?.makeToast("Copied to Clipboard".localized())
window?.endEditing(for: nil)
}

5
Clocker/Panel/UI/TimezoneDataSource.swift

@ -55,6 +55,9 @@ extension TimezoneDataSource: NSTableViewDataSource, NSTableViewDelegate {
cellView.sunriseSetTime.stringValue = operation.formattedSunriseTime(with: sliderValue)
cellView.sunriseImage.image = currentModel.isSunriseOrSunset ? Themer.shared().sunriseImage() : Themer.shared().sunsetImage()
if #available(macOS 10.14, *) {
cellView.sunriseImage.contentTintColor = currentModel.isSunriseOrSunset ? NSColor.systemYellow : NSColor.systemOrange
}
cellView.relativeDate.stringValue = operation.date(with: sliderValue, displayType: .panel)
cellView.rowNumber = row
cellView.customName.stringValue = currentModel.formattedTimezoneLabel()
@ -157,7 +160,7 @@ extension TimezoneDataSource: NSTableViewDataSource, NSTableViewDelegate {
return []
}
func showAlertForDeletingAHomeRow(_ row: Int, _ tableView: NSTableView) {
private func showAlertForDeletingAHomeRow(_ row: Int, _ tableView: NSTableView) {
NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert()

164
Clocker/Panel/UI/Toasty.swift

@ -0,0 +1,164 @@
// Copyright © 2015 Abhishek Banthia
import Foundation
import AppKit
extension CGRect {
static func center(of layer: CALayer) -> CGPoint {
let parentSize = layer.frame.size
return CGPoint(x: parentSize.width / 2, y: parentSize.height / 2)
}
static func center(of parent: NSView) -> CGPoint {
let parentSize = parent.frame.size
return CGPoint(x: parentSize.width / 2, y: parentSize.height / 6)
}
}
extension String {
func size(with fontSize: CGFloat) -> CGSize {
let attr: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: fontSize)]
let size = NSString(string: self).size(withAttributes: attr)
return size
}
}
fileprivate class HideAnimationDelegate: NSObject, CAAnimationDelegate {
private weak var view: NSView?
fileprivate init(view: NSView) {
self.view = view
}
fileprivate static func delegate(forView NSView: NSView) -> CAAnimationDelegate {
return HideAnimationDelegate(view: NSView)
}
fileprivate func animationDidStart(_ anim: CAAnimation) {
view?.layer?.opacity = 0.0
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
view?.removeFromSuperview()
view = nil
}
}
func hideAnimation(view: NSView, style: Style) {
let anim = CABasicAnimation(keyPath: "opacity")
let timing = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
anim.timingFunction = timing
let currentLayerTime = view.layer?.convertTime(CACurrentMediaTime(), from: nil)
anim.beginTime = currentLayerTime! + CFTimeInterval(style.fadeInOutDelay)
anim.duration = CFTimeInterval(style.fadeInOutDuration)
anim.fromValue = 1.0
anim.toValue = 0.0
anim.isRemovedOnCompletion = false
anim.delegate = HideAnimationDelegate.delegate(forView: view)
view.layer?.add(anim, forKey: "hide animation")
}
public protocol Style {
var fontSize: CGFloat {get}
var horizontalMargin: CGFloat {get}
var verticalMargin: CGFloat {get}
var cornerRadius: CGFloat {get}
var font: NSFont {get}
var backgroundColor: NSColor {get}
var foregroundColor: NSColor {get}
var fadeInOutDuration: CGFloat {get}
var fadeInOutDelay: CGFloat {get}
var labelOriginWithMargin: CGPoint {get}
var activitySize: CGSize {get}
}
extension Style {
public var labelOriginWithMargin: CGPoint {
return CGPoint(x: horizontalMargin, y: verticalMargin)
}
public var fontSize: CGFloat {return 12}
public var font: NSFont {
if let avenirFont = NSFont(name: "Avenir-Light", size: fontSize) {
return avenirFont
}
return NSFont.systemFont(ofSize: fontSize)
}
public var horizontalMargin: CGFloat {return 10}
public var verticalMargin: CGFloat {return 5}
public var cornerRadius: CGFloat {return 8}
public var backgroundColor: NSColor {return .black}
public var foregroundColor: NSColor {return .white}
public var activitySize: CGSize {return CGSize(width: 100, height: 100)}
public var fadeInOutDuration: CGFloat {return 1.0}
public var fadeInOutDelay: CGFloat {return 1.0}
}
public struct DefaultStyle: Style {
public static let shared = DefaultStyle()
}
private struct ToastKeys {
static var ActiveToast = "TSToastActiveToastKey"
}
class ToastView: NSView {
private let message: String
private let labelSize: CGSize
private let style: Style
init(message: String) {
self.message = message
self.style = DefaultStyle()
self.labelSize = message.size(with: style.fontSize)
let size = CGSize(
width: labelSize.width + style.horizontalMargin*2,
height: labelSize.height + style.verticalMargin*2
)
let rect = CGRect(origin: .zero, size: size)
super.init(frame: rect)
wantsLayer = true
}
required init?(coder aDecoder: NSCoder) { fatalError() }
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
if superview != nil {
configure()
}
}
private func configure() {
frame = superview?.bounds ?? NSRect.zero
let rect = CGRect(origin: style.labelOriginWithMargin, size: labelSize)
let sizeWithMargin = CGSize(
width: rect.width + style.horizontalMargin*2,
height: rect.height + style.verticalMargin*2
)
let rectWithMargin = CGRect(
origin: .zero, // position is manipulated later anyways
size: sizeWithMargin
)
// outside Container
let container = CALayer()
container.frame = rectWithMargin
container.position = CGRect.center(of: superview!)
container.backgroundColor = style.backgroundColor.cgColor
container.cornerRadius = style.cornerRadius
layer?.addSublayer(container)
// inside TextLayer
let text = CATextLayer()
text.frame = rect
text.position = CGRect.center(of: container)
text.string = message
text.font = NSFont.systemFont(ofSize: style.fontSize)
text.fontSize = style.fontSize
text.alignmentMode = .center
text.foregroundColor = style.foregroundColor.cgColor
text.backgroundColor = style.backgroundColor.cgColor
text.contentsScale = layer?.contentsScale ?? 0 // For Retina Display
container.addSublayer(text)
}
}
extension NSView {
public func makeToast(_ message: String) {
let toast = ToastView(message: message)
self.addSubview(toast)
hideAnimation(view: toast, style: DefaultStyle.shared)
}
}

4
Clocker/Preferences/About/AboutViewController.swift

@ -47,7 +47,7 @@ class AboutViewController: ParentViewController {
}
private func underlineTextForActionButton() {
let rangesInOrder = [NSRange(location: 3, length: 8),
let rangesInOrder = [NSRange(location: 3, length: 16),
NSRange(location: 7, length: privateFeedback.attributedTitle.length - 7),
NSRange(location: 27, length: 33),
NSRange(location: 42, length: 14)]
@ -57,7 +57,7 @@ class AboutViewController: ParentViewController {
supportClocker,
openSourceButton]
let localizedKeys = ["1. @n0shake on Twitter for quick comments",
let localizedKeys = ["1. @clocker_support on Twitter for quick comments",
"2. For Private Feedback",
"You can support Clocker by leaving a review on the App Store! :)",
"Help localize Clocker in your language by clicking here!"]

6
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -38,7 +38,8 @@ class AppearanceViewController: ParentViewController {
"hh:mm:ss a (07:08:09 PM)",
"-- 12 Hour w/o AM/PM --",
"hh:mm (07:08)",
"hh:mm:ss (07:08:09)"]
"hh:mm:ss (07:08:09)",
"Epoch Time"]
timeFormat.removeAllItems()
timeFormat.addItems(withTitles: supportedTimeFormats)
@ -179,9 +180,6 @@ class AppearanceViewController: ParentViewController {
let selection = NSNumber(value: sender.indexOfSelectedItem)
UserDefaults.standard.set(selection, forKey: CLSelectedTimeZoneFormatKey)
Logger.log(object: ["Time Format": sender.indexOfSelectedItem == 0 ? "12 Hour Format" : "24 Hour Format"], for: "Time Format Selected")
refresh(panel: true, floating: true)
if let selectedFormat = sender.selectedItem?.title,

1
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -17,6 +17,7 @@ class StatusItemHandler: NSObject {
var statusItem: NSStatusItem = {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.toolTip = "Clocker"
statusItem.highlightMode = false
return statusItem
}()

Loading…
Cancel
Save