Browse Source

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

pull/113/head
Abhishek Banthia 3 years ago
parent
commit
a289e7b929
  1. 33
      Clocker/AppDelegate.swift
  2. 6
      Clocker/Clocker.xcodeproj/project.pbxproj
  3. 34
      Clocker/Clocker/Utilities/CommonStrings.h
  4. 33
      Clocker/Clocker/Utilities/CommonStrings.m
  5. 1
      Clocker/ClockerUITests/ClockerUITests.m
  6. 130
      Clocker/ClockerUnitTests/ClockerUnitTests.swift
  7. 6
      Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift
  8. 38
      Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift
  9. 6
      Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift
  10. 8
      Clocker/Overall App/AppDefaults.swift
  11. 23
      Clocker/Overall App/DataStore.swift
  12. 2
      Clocker/Panel/FloatingWindowController.swift
  13. 8
      Clocker/Panel/PanelController.swift
  14. 68
      Clocker/Panel/ParentPanelController.swift
  15. 1
      Clocker/Preferences/About/AboutViewController.swift
  16. 2
      Clocker/Preferences/Appearance/AppearanceViewController.swift
  17. 115
      Clocker/Preferences/General/PreferencesViewController.swift
  18. 8
      Clocker/Preferences/Menu Bar/StatusContainerView.swift
  19. 2
      Clocker/Preferences/Menu Bar/StatusItemHandler.swift

33
Clocker/AppDelegate.swift

@ -53,9 +53,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
AppDefaults.initialize()
// For users, still on the old timezones, only migrate timezonezes once setClass has been called
migrateOverridenTimezones()
// Check if we can show the onboarding flow!
showOnboardingFlowIfEligible()
@ -68,28 +65,6 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
#endif
}
private func migrateOverridenTimezones() {
let defaults = UserDefaults.standard
if let shortCircuit = defaults.object(forKey: "MigrateIndividualTimezoneFormat") as? Bool, shortCircuit == true {
return
}
let timezones = DataStore.shared().timezones()
var migratedTimezones: [Data] = []
for encodedTimezone in timezones {
if let timezoneObject = TimezoneData.customObject(from: encodedTimezone) {
timezoneObject.setShouldOverrideGlobalTimeFormat(0)
migratedTimezones.append(NSKeyedArchiver.archivedData(withRootObject: timezoneObject))
}
}
if migratedTimezones.count > 0 {
defaults.set(migratedTimezones, forKey: CLDefaultPreferenceKey)
defaults.set(true, forKey: "MigrateIndividualTimezoneFormat")
}
}
public func applicationDockMenu(_: NSApplication) -> NSMenu? {
let menu = NSMenu(title: "Quick Access")
@ -279,12 +254,8 @@ open class AppDelegate: NSObject, NSApplicationDelegate {
NSApp.activate(ignoringOtherApps: true)
}
open func setupFloatingWindow() {
showFloatingWindow()
}
open func closeFloatingWindow() {
floatingWindow.window?.close()
open func setupFloatingWindow(_ hide: Bool) {
hide ? floatingWindow.window?.close() : showFloatingWindow()
}
func statusItemForPanel() -> StatusItemHandler {

6
Clocker/Clocker.xcodeproj/project.pbxproj

@ -136,7 +136,6 @@
9A3169C11D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A3169C01D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist */; };
9A43792A1BEC230A00F4E27F /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A4379291BEC230A00F4E27F /* libc++.tbd */; };
9A56DB801C1CFB73004CE6AF /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */; };
9A5951BD1C1D0A8D009C17AA /* CommonStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A5951BB1C1D0A8D009C17AA /* CommonStrings.m */; };
9A5E75E4204CC39700119939 /* ShortcutRecorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */; };
9A5E75E5204CC39700119939 /* ShortcutRecorder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BC1CE04F1600547EE7 /* ShortcutRecorder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9A5E75E8204CC39700119939 /* PTHotKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86E2BE1CE04F1600547EE7 /* PTHotKey.framework */; };
@ -386,8 +385,6 @@
9A4DC4E82337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
9A4DC4E92337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainMenu.xib; path = Clocker/MainMenu.xib; sourceTree = "<group>"; };
9A5951BB1C1D0A8D009C17AA /* CommonStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CommonStrings.m; path = Clocker/Utilities/CommonStrings.m; sourceTree = "<group>"; };
9A5951BC1C1D0A8D009C17AA /* CommonStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommonStrings.h; path = Clocker/Utilities/CommonStrings.h; sourceTree = "<group>"; };
9A5B1A8D1BECDFB700A77C68 /* Clocker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Clocker.entitlements; path = Clocker/Clocker.entitlements; sourceTree = "<group>"; };
9A5E6B9F1CAF71C1006E7C5C /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; };
9A6D93361CF3E82F005A8690 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; };
@ -727,8 +724,6 @@
isa = PBXGroup;
children = (
9A3169C01D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist */,
9A5951BB1C1D0A8D009C17AA /* CommonStrings.m */,
9A5951BC1C1D0A8D009C17AA /* CommonStrings.h */,
9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */,
9A13BAEC1CA88A76007C6CBE /* Localizable.strings */,
C2CCCD8120619C4C00F2DFC2 /* LocationController.swift */,
@ -1316,7 +1311,6 @@
35C36F452259D892002FA5C6 /* Strings.swift in Sources */,
35C36EF722595F14002FA5C6 /* FinalOnboardingViewController.swift in Sources */,
35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */,
9A5951BD1C1D0A8D009C17AA /* CommonStrings.m in Sources */,
357391872507277500D30819 /* TimeMarkerViewItem.swift in Sources */,
35C36F782259E1D0002FA5C6 /* Foundation + Additions.swift in Sources */,
35C36F16225961DA002FA5C6 /* Date+Inits.swift in Sources */,

34
Clocker/Clocker/Utilities/CommonStrings.h

@ -1,34 +0,0 @@
// Copyright © 2015 Abhishek Banthia
#import <Foundation/Foundation.h>
@interface CommonStrings : NSObject
extern NSString *const CLEmptyString;
extern NSString *const CLDefaultPreferenceKey;
extern NSString *const CLTimezoneName;
extern NSString *const CLCustomLabel;
extern NSString *const CL24hourFormatSelectedKey;
extern NSString *const CLDragSessionKey;
extern NSString *const CLTimezoneID;
extern NSString *const CLPlaceIdentifier;
extern NSString *const CLRelativeDateKey;
extern NSString *const CLThemeKey;
extern NSString *const CLShowDayInMenu;
extern NSString *const CLShowDateInMenu;
extern NSString *const CLShowPlaceInMenu;
extern NSString *const CLDisplayFutureSliderKey;
extern NSString *const CLStartAtLogin;
extern NSString *const CLShowAppInForeground;
extern NSString *const CLSunriseSunsetTime;
extern NSString *const CLLocationSearchURL;
extern NSString *const CLShowSecondsInMenubar;
extern NSString *const CLUserFontSizePreference;
extern NSString *const CLShowUpcomingEventView;
extern NSString *const CLShowAllDayEventsInUpcomingView;
extern NSString *const CLShowMeetingInMenubar;
extern NSString *const CLTruncateTextLength;
extern NSString *const CLFutureSliderRange;
extern NSString *const CLSelectedCalendars;
@end

33
Clocker/Clocker/Utilities/CommonStrings.m

@ -1,33 +0,0 @@
// Copyright © 2015 Abhishek Banthia
#import "CommonStrings.h"
@implementation CommonStrings
NSString *const CLEmptyString = @"";
NSString *const CLDefaultPreferenceKey = @"defaultPreferences";
NSString *const CLTimezoneName = @"formattedAddress";
NSString *const CLPlaceIdentifier = @"place_id";
NSString *const CLTimezoneID = @"timezoneID";
NSString *const CLCustomLabel = @"customLabel";
NSString *const CL24hourFormatSelectedKey = @"is24HourFormatSelected";
NSString *const CLDragSessionKey = @"public.text";
NSString *const CLRelativeDateKey = @"relativeDate";
NSString *const CLThemeKey = @"defaultTheme";
NSString *const CLDisplayFutureSliderKey = @"displayFutureSlider";
NSString *const CLShowDayInMenu = @"showDay";
NSString *const CLShowDateInMenu = @"showDate";
NSString *const CLShowPlaceInMenu = @"showPlaceName";
NSString *const CLStartAtLogin = @"startAtLogin";
NSString *const CLShowAppInForeground = @"displayAppAsForegroundApp";
NSString *const CLSunriseSunsetTime = @"showSunriseSetTime";
NSString *const CLShowSecondsInMenubar = @"showSeconds";
NSString *const CLUserFontSizePreference = @"userFontSize";
NSString *const CLShowUpcomingEventView = @"ShowUpcomingEventView";
NSString *const CLFutureSliderRange = @"sliderDayRange";
NSString *const CLShowAllDayEventsInUpcomingView = @"showAllDayEventsInUpcomingView";
NSString *const CLShowMeetingInMenubar = @"showMeetingInfoInMenubar";
NSString *const CLTruncateTextLength = @"truncateTextLength";
NSString *const CLSelectedCalendars = @"SelectedCalendars";
@end

1
Clocker/ClockerUITests/ClockerUITests.m

@ -1,7 +1,6 @@
// Copyright © 2015 Abhishek Banthia
#import <XCTest/XCTest.h>
#import "CommonStrings.h"
@interface ClockerUITests : XCTestCase

130
Clocker/ClockerUnitTests/ClockerUnitTests.swift

@ -94,27 +94,54 @@ class ClockerUnitTests: XCTestCase {
func testAddingATimezoneToDefaults() {
let timezoneData = TimezoneData(with: california)
let defaults = UserDefaults.standard
let currentFavourites = (defaults.object(forKey: CLDefaultPreferenceKey) as? [Data]) ?? []
let currentFavourites = DataStore.shared().timezones()
let oldCount = currentFavourites.count
let operationsObject = TimezoneDataOperations(with: timezoneData)
operationsObject.saveObject()
let newDefaults = UserDefaults.standard.object(forKey: CLDefaultPreferenceKey) as? [Data]
let newDefaults = DataStore.shared().timezones()
XCTAssert(newDefaults != nil)
XCTAssert(newDefaults?.count == oldCount + 1)
XCTAssert(newDefaults.isEmpty == false)
XCTAssert(newDefaults.count == oldCount + 1)
}
func testDeletingATimezone() {
let defaults = UserDefaults.standard
func testDecoding() {
let timezone1 = TimezoneData.customObject(from: nil)
XCTAssertNotNil(timezone1)
guard var currentFavourites = defaults.object(forKey: CLDefaultPreferenceKey) as? [Data] else {
XCTFail("Default preferences aren't in the correct format")
return
}
let data = Data()
let timezone2 = TimezoneData.customObject(from: data)
XCTAssertNil(timezone2)
}
func testDescription() {
let timezoneData = TimezoneData(with: california)
XCTAssertFalse(timezoneData.description.isEmpty)
XCTAssertFalse(timezoneData.debugDescription.isEmpty)
}
func testHashing() {
let timezoneData = TimezoneData(with: california)
XCTAssert(timezoneData.hash != -1)
timezoneData.placeID = nil
timezoneData.timezoneID = nil
XCTAssert(timezoneData.hash == -1)
}
func testBadInputDictionaryForInitialization() {
let badInput: [String: Any] = ["customLabel": "",
"latitude": "41.2565369",
"longitude": "-95.9345034"]
let badTimezoneData = TimezoneData(with: badInput)
XCTAssertEqual(badTimezoneData.placeID, "Error")
XCTAssertEqual(badTimezoneData.timezoneID, "Error")
XCTAssertEqual(badTimezoneData.formattedAddress, "Error")
}
func testDeletingATimezone() {
var currentFavourites = DataStore.shared().timezones()
// Check if timezone with test identifier is present.
let filteredCount = currentFavourites.filter {
let timezone = TimezoneData.customObject(from: $0)
@ -128,14 +155,14 @@ class ClockerUnitTests: XCTestCase {
operationsObject.saveObject()
}
let oldCount = (defaults.object(forKey: CLDefaultPreferenceKey) as? [Data])?.count ?? 0
let oldCount = DataStore.shared().timezones().count
currentFavourites = currentFavourites.filter {
let timezone = TimezoneData.customObject(from: $0)
return timezone?.placeID != "TestIdentifier"
}
defaults.set(currentFavourites, forKey: CLDefaultPreferenceKey)
DataStore.shared().setTimezones(currentFavourites)
XCTAssertTrue(currentFavourites.count == oldCount - 1)
}
@ -143,10 +170,10 @@ class ClockerUnitTests: XCTestCase {
// The below test might fail outside California or if DST is active!
// CI is calibrated to be on LA timezone!
func testTimeDifference() {
XCTAssertTrue(operations.timeDifference() == ", 10h 30m ahead", "Difference was unexpectedly: \(operations.timeDifference())")
XCTAssertTrue(operations.timeDifference() == ", 9h 30m ahead", "Difference was unexpectedly: \(operations.timeDifference())")
XCTAssertTrue(californiaOperations.timeDifference() == ", 3h behind", "Difference was unexpectedly: \(californiaOperations.timeDifference())")
XCTAssertTrue(floridaOperations.timeDifference() == "", "Difference was unexpectedly: \(floridaOperations.timeDifference())")
XCTAssertTrue(aucklandOperations.timeDifference() == ", 18h ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())")
XCTAssertTrue(aucklandOperations.timeDifference() == ", 17h ahead", "Difference was unexpectedly: \(aucklandOperations.timeDifference())")
XCTAssertTrue(omahaOperations.timeDifference() == ", an hour behind", "Difference was unexpectedly: \(omahaOperations.timeDifference())")
}
@ -207,6 +234,10 @@ class ClockerUnitTests: XCTestCase {
dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss")
// Wrong input
dataObject.setShouldOverrideGlobalTimeFormat(0) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(88) == "h:mm a")
}
func testTimezoneFormatWithDefaultSetAs24HourFormat() {
@ -214,7 +245,8 @@ class ClockerUnitTests: XCTestCase {
UserDefaults.standard.set(NSNumber(value: 1), forKey: CLSelectedTimeZoneFormatKey) // Set to 24-Hour Format
dataObject.setShouldOverrideGlobalTimeFormat(0)
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm")
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "HH:mm",
"Unexpected format returned: \(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()))")
dataObject.setShouldOverrideGlobalTimeFormat(1) // 12-Hour Format
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "h:mm a")
@ -242,17 +274,73 @@ class ClockerUnitTests: XCTestCase {
dataObject.setShouldOverrideGlobalTimeFormat(11) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "hh:mm:ss")
dataObject.setShouldOverrideGlobalTimeFormat(12) // 12-hour with preceding zero and seconds
XCTAssertTrue(dataObject.timezoneFormat(DataStore.shared().timezoneFormat()) == "epoch")
}
func testSecondsDisplayForOverridenTimezone() {
let dataObject = TimezoneData(with: california)
UserDefaults.standard.set(NSNumber(value: 1), forKey: CLSelectedTimeZoneFormatKey) // Set to 24-Hour Format
// Test default behaviour
let timezoneWithSecondsKeys = [4,5,8,11]
for timezoneKey in timezoneWithSecondsKeys {
dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey)
XCTAssertTrue(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
}
let timezoneWithoutSecondsKeys = [1,2,7,10]
for timezoneKey in timezoneWithoutSecondsKeys {
dataObject.setShouldOverrideGlobalTimeFormat(timezoneKey)
XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
}
// Test wrong override timezone key
let wrongTimezoneKey = 88
dataObject.setShouldOverrideGlobalTimeFormat(wrongTimezoneKey)
XCTAssertFalse(dataObject.shouldShowSeconds(DataStore.shared().timezoneFormat()))
// Test wrong global preference key
dataObject.setShouldOverrideGlobalTimeFormat(0)
XCTAssertFalse(dataObject.shouldShowSeconds(88))
}
func testTimezoneRetrieval() {
let dataObject = TimezoneData(with: mumbai)
let autoupdatingTimezone = TimeZone.autoupdatingCurrent.identifier
XCTAssertEqual(dataObject.timezone(), "Asia/Calcutta")
// Unlikely
dataObject.timezoneID = nil
XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone)
dataObject.isSystemTimezone = true
XCTAssertEqual(dataObject.timezone(), autoupdatingTimezone)
}
func testFormattedLabel() {
let dataObject = TimezoneData(with: mumbai)
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model.")
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Ghar", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
dataObject.customLabel = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Mumbai", "Incorrect custom label returned by model.")
dataObject.setLabel("")
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Mumbai", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
dataObject.formattedAddress = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model.")
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Asia", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
dataObject.setLabel("Jogeshwari")
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Jogeshwari", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
// Unlikely scenario
dataObject.setLabel("")
dataObject.timezoneID = "GMT"
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "GMT", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
// Another unlikely scenario
dataObject.setLabel("")
dataObject.timezoneID = nil
XCTAssertTrue(dataObject.formattedTimezoneLabel() == "Error", "Incorrect custom label returned by model \(dataObject.formattedTimezoneLabel())")
}
func testEquality() {

6
Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift

@ -16,7 +16,7 @@ class StandardMenubarHandlerTests: XCTestCase {
func testValidStandardMenubarHandler_returnMenubarTitle() {
// Wipe all timezones from UserDefaults
UserDefaults.standard.setValue(nil, forKey: CLDefaultPreferenceKey)
DataStore.shared().setTimezones(nil)
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
@ -43,7 +43,7 @@ class StandardMenubarHandlerTests: XCTestCase {
func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() {
// Wipe all timezones from UserDefaults
UserDefaults.standard.setValue(nil, forKey: CLDefaultPreferenceKey)
DataStore.shared().setTimezones(nil)
// Save a menubar selected timezone
let dataObject = TimezoneData(with: mumbai)
@ -57,7 +57,7 @@ class StandardMenubarHandlerTests: XCTestCase {
func testUnfavouritedTimezone_returnNilMenubarString() {
// Wipe all timezones from UserDefaults
UserDefaults.standard.setValue(nil, forKey: CLDefaultPreferenceKey)
DataStore.shared().setTimezones(nil)
let menubarHandler = MenubarHandler()
let emptyMenubarString = menubarHandler.titleForMenubar()
// Returns early because DataStore.menubarTimezones is nil

38
Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift

@ -198,7 +198,6 @@ public class TimezoneData: NSObject, NSCoding {
} else if shouldOverride == 4 {
overrideFormat = .twelveHourWithSeconds
} else if shouldOverride == 5 {
print("Setting override format to five")
overrideFormat = .twentyHourWithSeconds
} else if shouldOverride == 7 {
overrideFormat = .twelveHourPrecedingZero
@ -211,7 +210,7 @@ public class TimezoneData: NSObject, NSCoding {
} else if shouldOverride == 12 {
overrideFormat = .epochTime
} else {
assertionFailure("Chosen a wrong timezone format")
Logger.info("Chosen a wrong timezone format: \(shouldOverride)")
}
}
@ -226,16 +225,6 @@ public class TimezoneData: NSObject, NSCoding {
return timezone
}
if let name = formattedAddress, let placeIdentifier = placeID, let timezoneIdentifier = timezoneID {
let errorDictionary = [
"Formatted Address": name,
"Place Identifier": placeIdentifier,
"TimezoneID": timezoneIdentifier,
]
Logger.log(object: errorDictionary, for: "Error fetching timezone() in TimezoneData")
}
return TimeZone.autoupdatingCurrent.identifier
}
@ -273,7 +262,9 @@ public class TimezoneData: NSObject, NSCoding {
return formatInString.contains("ss")
}
let formatInString = TimezoneData.values[NSNumber(integerLiteral: overrideFormat.rawValue)] ?? DateFormat.twelveHour
// We subtract 1 because the timezone format in the dropdown contains 1 extra row for "Respecting global preferences"
let key = NSNumber(integerLiteral: overrideFormat.rawValue - 1)
let formatInString = TimezoneData.values[key] ?? DateFormat.twelveHour
return formatInString.contains("ss")
}
@ -285,17 +276,6 @@ public class TimezoneData: NSObject, NSCoding {
return placeIdentifier.hashValue ^ timezone.hashValue
}
static func == (lhs: TimezoneData, rhs: TimezoneData) -> Bool {
return lhs.placeID == rhs.placeID
}
public override func isEqual(to object: Any?) -> Bool {
if let other = object as? TimezoneData {
return placeID == other.placeID
}
return false
}
public override func isEqual(_ object: Any?) -> Bool {
guard let compared = object as? TimezoneData else {
return false
@ -317,17 +297,17 @@ public extension TimezoneData {
private func objectDescription() -> String {
let customString = """
TimezoneID: \(timezoneID ?? "Error")
TimezoneID: \(String(describing: timezoneID))
Formatted Address: \(formattedAddress ?? "Error")
Custom Label: \(customLabel ?? "Error")
Latitude: \(latitude ?? -0.0)
Longitude: \(longitude ?? -0.0)
Place Identifier: \(placeID ?? "Error")
Place Identifier: \(String(describing: placeID))
Is Favourite: \(isFavourite)
Sunrise Time: \(sunriseTime?.debugDescription ?? "N/A")
Sunset Time: \(sunsetTime?.debugDescription ?? "N/A")
Sunrise Time: \(String(describing: sunriseTime))
Sunset Time: \(String(describing: sunsetTime))
Selection Type: \(selectionType.rawValue)
Note: \(note ?? "Error")
Note: \(String(describing: note))
Is System Timezone: \(isSystemTimezone)
Override: \(overrideFormat)
"""

6
Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift

@ -14,7 +14,8 @@ class TimezoneDataEqualityTests: XCTestCase {
timezone2.timezoneID = "Africa/Banjul"
timezone2.formattedAddress = "SameLabel"
XCTAssertNotEqual(timezone1, timezone2)
XCTAssertFalse(timezone1 == timezone2) // Test ==
XCTAssertNotEqual(timezone1, timezone2) // Test isEqual
}
func testEqualityWhenTimezonesLabelsDiffer() {
@ -26,6 +27,7 @@ class TimezoneDataEqualityTests: XCTestCase {
timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier
timezone2.formattedAddress = "DifferentLabel"
XCTAssertFalse(timezone1 == timezone2)
XCTAssertNotEqual(timezone1, timezone2)
}
@ -40,6 +42,7 @@ class TimezoneDataEqualityTests: XCTestCase {
timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier
timezone2.formattedAddress = "DifferentLabel"
XCTAssertTrue(timezone1 == timezone2)
XCTAssertEqual(timezone1, timezone2)
}
@ -54,6 +57,7 @@ class TimezoneDataEqualityTests: XCTestCase {
timezone2.timezoneID = TimeZone.autoupdatingCurrent.identifier
timezone2.formattedAddress = "DifferentLabel"
XCTAssertFalse(timezone1 == timezone2)
XCTAssertNotEqual(timezone1, timezone2)
}
}

8
Clocker/Overall App/AppDefaults.swift

@ -20,8 +20,9 @@ class AppDefaults {
private class func initializeDefaults() {
let userDefaults = UserDefaults.standard
let dataStore = DataStore.shared()
let timezones = userDefaults.object(forKey: CLDefaultPreferenceKey)
let timezones = dataStore.timezones()
let selectedCalendars = userDefaults.object(forKey: CLSelectedCalendars)
// Now delete the old preferences
@ -30,7 +31,7 @@ class AppDefaults {
// Register the usual suspects
userDefaults.register(defaults: defaultsDictionary())
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
dataStore.setTimezones(timezones)
userDefaults.set(selectedCalendars, forKey: CLSelectedCalendars)
// Set the theme default as Light!
@ -39,7 +40,7 @@ class AppDefaults {
// If we already have timezones to display in menubar, do nothing.
// Else, we switch the menubar mode default to compact mode for new users
if userDefaults.bool(forKey: CLDefaultMenubarMode) == false {
if let menubarFavourites = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data], menubarFavourites.isEmpty == false {
if let menubarFavourites = dataStore.menubarTimezones(), menubarFavourites.isEmpty == false {
userDefaults.set(1, forKey: CLMenubarCompactMode)
} else {
userDefaults.set(0, forKey: CLMenubarCompactMode)
@ -137,6 +138,7 @@ extension UserDefaults {
func wipeIfNeccesary() {
if let bundleID = Bundle.main.bundleIdentifier, object(forKey: "PreferencesHaveBeenWiped") == nil {
Logger.info("Wiping all user defaults")
removePersistentDomain(forName: bundleID)
set(true, forKey: "PreferencesHaveBeenWiped")
}

23
Clocker/Overall App/DataStore.swift

@ -40,6 +40,11 @@ class DataStore: NSObject {
}
func timezones() -> [Data] {
if let cloudPreferences = NSUbiquitousKeyValueStore().object(forKey: CLDefaultPreferenceKey) as? [Data] {
Logger.info("Returning preferences from NSUbiquitousKeyValueStore")
return cloudPreferences
}
guard let preferences = userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data] else {
return []
}
@ -47,6 +52,13 @@ class DataStore: NSObject {
return preferences
}
func setTimezones(_ timezones: [Data]?) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
// iCloud sync
NSUbiquitousKeyValueStore().set(timezones, forKey: CLDefaultPreferenceKey)
NSUbiquitousKeyValueStore().synchronize()
}
func menubarTimezones() -> [Data]? {
return timezones().filter {
let customTimezone = TimezoneData.customObject(from: $0)
@ -70,10 +82,6 @@ class DataStore: NSObject {
return shouldDisplayDateInMenubar
}
func setTimezones(_ timezones: [Data]) {
userDefaults.set(timezones, forKey: CLDefaultPreferenceKey)
}
func retrieve(key: String) -> Any? {
return userDefaults.object(forKey: key)
}
@ -81,10 +89,9 @@ class DataStore: NSObject {
func addTimezone(_ timezone: TimezoneData) {
let encodedTimezone = NSKeyedArchiver.archivedData(withRootObject: timezone)
var defaults: [Data] = (userDefaults.object(forKey: CLDefaultPreferenceKey) as? [Data]) ?? []
var defaults: [Data] = timezones()
defaults.append(encodedTimezone)
userDefaults.set(defaults, forKey: CLDefaultPreferenceKey)
setTimezones(defaults)
}
func removeLastTimezone() {
@ -98,7 +105,7 @@ class DataStore: NSObject {
Logger.log(object: [:], for: "Undo Action Executed during Onboarding")
userDefaults.set(currentLineup, forKey: CLDefaultPreferenceKey)
setTimezones(currentLineup)
}
private func shouldDisplayHelper(_ key: String) -> Bool {

2
Clocker/Panel/FloatingWindowController.swift

@ -49,7 +49,7 @@ class FloatingWindowController: ParentPanelController {
}
override func showNotesPopover(forRow row: Int, relativeTo positioningRect: NSRect, andButton target: NSButton!) -> Bool {
guard let popover = morePopover else {
guard let popover = additionalOptionsPopover else {
return false
}

8
Clocker/Panel/PanelController.swift

@ -302,7 +302,7 @@ class PanelController: ParentPanelController {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 0.1
window?.animator().alphaValue = 0
morePopover?.close()
additionalOptionsPopover?.close()
NSAnimationContext.endGrouping()
window?.orderOut(nil)
@ -329,11 +329,11 @@ class PanelController: ParentPanelController {
}
override func showNotesPopover(forRow row: Int, relativeTo positioningRect: NSRect, andButton target: NSButton!) -> Bool {
if morePopover == nil {
morePopover = NSPopover()
if additionalOptionsPopover == nil {
additionalOptionsPopover = NSPopover()
}
guard let popover = morePopover else {
guard let popover = additionalOptionsPopover else {
return false
}

68
Clocker/Panel/ParentPanelController.swift

@ -33,7 +33,7 @@ class ParentPanelController: NSWindowController {
var previousPopoverRow: Int = -1
var morePopover: NSPopover?
var additionalOptionsPopover: NSPopover?
var datasource: TimezoneDataSource?
@ -153,36 +153,53 @@ class ParentPanelController: NSWindowController {
override func awakeFromNib() {
super.awakeFromNib()
// Setup table
mainTableView.backgroundColor = NSColor.clear
mainTableView.selectionHighlightStyle = .none
mainTableView.enclosingScrollView?.hasVerticalScroller = false
if #available(OSX 11.0, *) {
mainTableView.style = .fullWidth
}
// Setup images
let sharedThemer = Themer.shared()
shutdownButton.image = sharedThemer.shutdownImage()
preferencesButton.image = sharedThemer.preferenceImage()
pinButton.image = sharedThemer.pinImage()
sharingButton.image = sharedThemer.sharingImage()
if let upcomingView = upcomingEventContainerView {
upcomingView.setAccessibility("UpcomingEventView")
}
// Setup KVO observers for user default changes
setupObservers()
updateReviewViewFontColor()
// Setup layers
futureSliderView.wantsLayer = true
reviewView.wantsLayer = true
// Setup notifications
NotificationCenter.default.addObserver(self,
selector: #selector(themeChanged),
name: Notification.Name.themeDidChange,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(systemTimezoneDidChange),
name: NSNotification.Name.NSSystemTimeZoneDidChange,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(ubiquitousStoreDidChange),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: nil)
// Setup upcoming events view
upcomingEventContainerView.setAccessibility("UpcomingEventView")
determineUpcomingViewVisibility()
setupUpcomingEventViewCollectionViewIfNeccesary()
// Setup colors based on the curren theme
themeChanged()
// UI adjustments based on user preferences
if DataStore.shared().timezones().isEmpty || DataStore.shared().shouldDisplay(.futureSlider) == false {
futureSliderView.isHidden = true
if modernContainerView != nil {
@ -203,22 +220,10 @@ class ParentPanelController: NSWindowController {
}
}
// More UI adjustments
sharingButton.sendAction(on: .leftMouseDown)
adjustFutureSliderBasedOnPreferences()
NotificationCenter.default.addObserver(self,
selector: #selector(timezoneGonnaChange),
name: NSNotification.Name.NSSystemTimeZoneDidChange,
object: nil)
if #available(OSX 11.0, *) {
mainTableView.style = .fullWidth
}
setupModernSliderIfNeccessary()
setupUpcomingEventViewCollectionViewIfNeccesary()
if roundedDateView != nil {
setupRoundedDateView()
}
@ -231,7 +236,7 @@ class ParentPanelController: NSWindowController {
roundedDateView.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
@objc func timezoneGonnaChange() {
@objc func systemTimezoneDidChange() {
OperationQueue.main.addOperation {
/*
let locationController = LocationController.sharedController()
@ -242,6 +247,14 @@ class ParentPanelController: NSWindowController {
}
}
// Backing defaults changed
@objc func ubiquitousStoreDidChange() {
OperationQueue.main.addOperation {
self.mainTableView.reloadData()
self.setScrollViewConstraint()
}
}
private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) {
let timezones = DataStore.shared().timezones()
@ -366,7 +379,7 @@ class ParentPanelController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
morePopover = NSPopover()
additionalOptionsPopover = NSPopover()
}
func screenHeight() -> CGFloat {
@ -640,7 +653,7 @@ class ParentPanelController: NSWindowController {
func showNotesPopover(forRow row: Int, relativeTo _: NSRect, andButton target: NSButton!) -> Bool {
let defaults = DataStore.shared().timezones()
guard let popover = morePopover else {
guard let popover = additionalOptionsPopover else {
assertionFailure("Data was unexpectedly nil")
return false
}
@ -773,9 +786,9 @@ class ParentPanelController: NSWindowController {
close()
if inverseSelection.isEqual(to: NSNumber(value: 1)) {
sharedDelegate.setupFloatingWindow()
sharedDelegate.setupFloatingWindow(false)
} else {
sharedDelegate.closeFloatingWindow()
sharedDelegate.setupFloatingWindow(true)
sharedDelegate.setPanelDefaults()
}
@ -834,7 +847,7 @@ class ParentPanelController: NSWindowController {
if notePopover != nil, let isShown = notePopover?.popover?.isShown, isShown {
notePopover?.popover?.close()
}
morePopover = nil
additionalOptionsPopover = nil
}
// MARK: Review
@ -988,6 +1001,12 @@ class ParentPanelController: NSWindowController {
NSWorkspace.shared.open(sourceURL)
}
@objc func openFAQs() {
guard let sourceURL = URL(string: AboutUsConstants.FAQsLink) else { return }
NSWorkspace.shared.open(sourceURL)
}
@IBAction func showMoreOptions(_ sender: NSButton) {
let menuItem = NSMenu(title: "More Options")
let terminateOption = NSMenuItem(title: "Quit Clocker",
@ -1010,6 +1029,7 @@ class ParentPanelController: NSWindowController {
clockerVersionInfo.isEnabled = false
menuItem.addItem(openPreferences)
menuItem.addItem(rateClocker)
menuItem.addItem(withTitle: "FAQs", action: #selector(openFAQs), keyEquivalent: "")
menuItem.addItem(sendFeedback)
menuItem.addItem(localizeClocker)
menuItem.addItem(NSMenuItem.separator())

1
Clocker/Preferences/About/AboutViewController.swift

@ -11,6 +11,7 @@ struct AboutUsConstants {
static let TwitterFollowIntentLink = "https://twitter.com/intent/follow?screen_name=clocker_support"
static let AppStoreLink = "macappstore://itunes.apple.com/us/app/clocker/id1056643111?action=write-review"
static let CrowdInLocalizationLink = "https://crwd.in/clocker"
static let FAQsLink = "https://abhishekbanthia.com/clocker/faq"
}
class AboutViewController: ParentViewController {

2
Clocker/Preferences/Appearance/AppearanceViewController.swift

@ -189,7 +189,7 @@ class AppearanceViewController: ParentViewController {
if let selectedFormat = sender.selectedItem?.title,
selectedFormat.contains("ss") {
print("Seconds are contained")
Logger.info("Selected format contains timezone format")
guard let panelController = PanelController.panel() else { return }
panelController.pauseTimer()
}

115
Clocker/Preferences/General/PreferencesViewController.swift

@ -18,50 +18,39 @@ struct PreferencesConstants {
static let hotKeyPathIdentifier = "values.globalPing"
}
class TableHeaderViewCell: NSTableHeaderCell {
var backgroundColour: NSColor = NSColor.black {
didSet {
backgroundColor = backgroundColour
}
}
class PreferencesViewController: ParentViewController {
override init(textCell: String) {
super.init(textCell: textCell)
let attributedParagraphStyle = NSMutableParagraphStyle()
attributedParagraphStyle.alignment = .left
attributedStringValue = NSAttributedString(string: textCell,
attributes: [.foregroundColor: Themer.shared().mainTextColor(),
.font: NSFont(name: "Avenir", size: 14)!,
.paragraphStyle: attributedParagraphStyle])
backgroundColor = Themer.shared().textBackgroundColor()
}
@IBOutlet private var placeholderLabel: NSTextField!
@IBOutlet private var timezoneTableView: NSTableView!
@IBOutlet private var availableTimezoneTableView: NSTableView!
@IBOutlet private var timezonePanel: Panelr!
@IBOutlet private var progressIndicator: NSProgressIndicator!
@IBOutlet private var addButton: NSButton!
@IBOutlet private var recorderControl: SRRecorderControl!
@IBOutlet private var closeButton: NSButton!
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@IBOutlet private var timezoneSortButton: NSButton!
@IBOutlet private var timezoneNameSortButton: NSButton!
@IBOutlet private var labelSortButton: NSButton!
@IBOutlet private var deleteButton: NSButton!
@IBOutlet private var addTimezoneButton: NSButton!
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
super.draw(withFrame: cellFrame, in: controlView)
if !controlView.isHidden {
backgroundColor?.setFill()
cellFrame.fill()
drawInterior(withFrame: cellFrame, in: controlView)
}
}
@IBOutlet private var searchField: NSSearchField!
@IBOutlet private var messageLabel: NSTextField!
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
if !controlView.isHidden {
if let avenirFont = NSFont(name: "Avenir", size: 14) {
font = avenirFont
}
textColor = NSColor.white
let rect = titleRect(forBounds: cellFrame)
attributedStringValue.draw(in: rect)
}
}
}
@IBOutlet private var tableview: NSView!
@IBOutlet private var additionalSortOptions: NSView!
@IBOutlet var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton!
// Sorting
private var arePlacesSortedInAscendingOrder = false
private var arePlacesSortedInAscendingTimezoneOrder = false
private var isTimezoneSortOptionSelected = false
private var isTimezoneNameSortOptionSelected = false
private var isLabelOptionSelected = false
class PreferencesViewController: ParentViewController {
private var isActivityInProgress = false {
didSet {
OperationQueue.main.addOperation {
@ -76,6 +65,11 @@ class PreferencesViewController: ParentViewController {
return DataStore.shared().timezones()
}
private var themeDidChangeNotification: NSObjectProtocol?
// Selected Timezones Data Source
private var selectionsDataSource: PreferencesDataSource!
// Search Results Data Source Handler
private var searchResultsDataSource: SearchDataSource!
private lazy var startupManager = StartupManager()
private var dataTask: URLSessionDataTask? = .none
@ -94,43 +88,7 @@ class PreferencesViewController: ParentViewController {
return apiKey
}()
// Sorting
private var arePlacesSortedInAscendingOrder = false
private var arePlacesSortedInAscendingTimezoneOrder = false
private var isTimezoneSortOptionSelected = false
private var isTimezoneNameSortOptionSelected = false
private var isLabelOptionSelected = false
@IBOutlet private var placeholderLabel: NSTextField!
@IBOutlet private var timezoneTableView: NSTableView!
@IBOutlet private var availableTimezoneTableView: NSTableView!
@IBOutlet private var timezonePanel: Panelr!
@IBOutlet private var progressIndicator: NSProgressIndicator!
@IBOutlet private var addButton: NSButton!
@IBOutlet private var recorderControl: SRRecorderControl!
@IBOutlet private var closeButton: NSButton!
@IBOutlet private var timezoneSortButton: NSButton!
@IBOutlet private var timezoneNameSortButton: NSButton!
@IBOutlet private var labelSortButton: NSButton!
@IBOutlet private var deleteButton: NSButton!
@IBOutlet private var addTimezoneButton: NSButton!
@IBOutlet private var searchField: NSSearchField!
@IBOutlet private var messageLabel: NSTextField!
@IBOutlet private var tableview: NSView!
@IBOutlet private var additionalSortOptions: NSView!
@IBOutlet var startAtLoginLabel: NSTextField!
@IBOutlet var startupCheckbox: NSButton!
private var themeDidChangeNotification: NSObjectProtocol?
// Selected Timezones Data Source
private var selectionsDataSource: PreferencesDataSource!
// Search Results Data Source Handler
private var searchResultsDataSource: SearchDataSource!
override func viewDidLoad() {
super.viewDidLoad()
@ -530,12 +488,7 @@ extension PreferencesViewController {
}
private func presentError(_ errorMessage: String) {
if errorMessage == PreferencesConstants.offlineErrorMessage {
placeholderLabel.placeholderString = PreferencesConstants.noInternetConnectivityError
} else {
placeholderLabel.placeholderString = PreferencesConstants.tryAgainMessage
}
placeholderLabel.placeholderString = errorMessage == PreferencesConstants.offlineErrorMessage ? PreferencesConstants.noInternetConnectivityError : PreferencesConstants.tryAgainMessage
isActivityInProgress = false
}

8
Clocker/Preferences/Menu Bar/StatusContainerView.swift

@ -177,7 +177,13 @@ class StatusContainerView: NSView {
if newWidth != frame.size.width, newWidth > frame.size.width + 2.0 {
Logger.info("Correcting our width to \(newWidth) and the previous width was \(frame.size.width)")
frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: newWidth, height: frame.size.height)
// NSView move animation
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
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
}) {}
}
}
}

2
Clocker/Preferences/Menu Bar/StatusItemHandler.swift

@ -150,7 +150,7 @@ class StatusItemHandler: NSObject {
}
private func retrieveSyncedMenubarTimezones() -> [Data] {
let defaultPreferences = DataStore.shared().retrieve(key: CLDefaultPreferenceKey) as? [Data] ?? []
let defaultPreferences = DataStore.shared().timezones()
let menubarTimezones = defaultPreferences.filter { data -> Bool in
if let timezoneObj = TimezoneData.customObject(from: data) {

Loading…
Cancel
Save