diff --git a/Clocker/AppDelegate.swift b/Clocker/AppDelegate.swift index f15781e..cfcac83 100644 --- a/Clocker/AppDelegate.swift +++ b/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") diff --git a/Clocker/Clocker.xcodeproj/project.pbxproj b/Clocker/Clocker.xcodeproj/project.pbxproj index 2372224..44bec66 100755 --- a/Clocker/Clocker.xcodeproj/project.pbxproj +++ b/Clocker/Clocker.xcodeproj/project.pbxproj @@ -123,7 +123,6 @@ 35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */; }; 35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */; }; 35DFBCEF26A8468900D6648B /* ConfigExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DFBCEE26A8468900D6648B /* ConfigExport.swift */; }; - 35DFBCF126A8479000D6648B /* Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 35DFBCF026A8479000D6648B /* Keys.plist */; }; 35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E65124268EDD2E00E3E1E3 /* Toasty.swift */; }; 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */; }; 9A0385C0269E8891003B5E72 /* PermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0385BF269E8891003B5E72 /* PermissionsTests.swift */; }; @@ -131,12 +130,12 @@ 9A13BAD61CA87F08007C6CBE /* Panel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAD81CA87F08007C6CBE /* Panel.xib */; }; 9A13BAE01CA882FA007C6CBE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAE21CA882FA007C6CBE /* InfoPlist.strings */; }; 9A13BAEA1CA88A76007C6CBE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A13BAEC1CA88A76007C6CBE /* Localizable.strings */; }; + 9A159CEB27B00E3300570529 /* Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A159CEA27B00E3300570529 /* Keys.plist */; }; 9A20A04B1C4DEED200FB45AB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A20A04A1C4DEED200FB45AB /* IOKit.framework */; }; 9A24A1881ED902CC0095201E /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A24A1871ED902CC0095201E /* EventKit.framework */; }; 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 */; }; @@ -362,13 +361,13 @@ 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemindersHandler.swift; sourceTree = ""; }; 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 35DFBCEE26A8468900D6648B /* ConfigExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigExport.swift; sourceTree = ""; }; - 35DFBCF026A8479000D6648B /* Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Keys.plist; path = ../Internal/Keys.plist; sourceTree = ""; }; 35E65124268EDD2E00E3E1E3 /* Toasty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toasty.swift; sourceTree = ""; }; 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardMenubarHandlerTests.swift; sourceTree = ""; }; 9A0385BF269E8891003B5E72 /* PermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsTests.swift; sourceTree = ""; }; 9A13BAD71CA87F08007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/Panel.xib; sourceTree = ""; }; 9A13BAE11CA882FA007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 9A13BAEB1CA88A76007C6CBE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 9A159CEA27B00E3300570529 /* Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Keys.plist; path = Internal/Keys.plist; sourceTree = ""; }; 9A20A04A1C4DEED200FB45AB /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 9A20A06F1C4E804D00FB45AB /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 9A24A1871ED902CC0095201E /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = System/Library/Frameworks/EventKit.framework; sourceTree = SDKROOT; }; @@ -386,8 +385,6 @@ 9A4DC4E82337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; 9A4DC4E92337F5D600F03FA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 9A56DB7D1C1CFB73004CE6AF /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainMenu.xib; path = Clocker/MainMenu.xib; sourceTree = ""; }; - 9A5951BB1C1D0A8D009C17AA /* CommonStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CommonStrings.m; path = Clocker/Utilities/CommonStrings.m; sourceTree = ""; }; - 9A5951BC1C1D0A8D009C17AA /* CommonStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommonStrings.h; path = Clocker/Utilities/CommonStrings.h; sourceTree = ""; }; 9A5B1A8D1BECDFB700A77C68 /* Clocker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Clocker.entitlements; path = Clocker/Clocker.entitlements; sourceTree = ""; }; 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 */, @@ -914,7 +909,7 @@ DD4F7BF913C30F9F00825C6E = { isa = PBXGroup; children = ( - 35DFBCF026A8479000D6648B /* Keys.plist */, + 9A159CEA27B00E3300570529 /* Keys.plist */, 35B2FEE4259A2C25005DA84D /* CoreModelKit */, 35B2FED4259A2244005DA84D /* CoreLoggerKit */, 35B2FEB1259A1649005DA84D /* StartupKit */, @@ -1221,6 +1216,7 @@ 35C36F972259EBB1002FA5C6 /* AppFeedbackWindow.xib in Resources */, 9A13BAE01CA882FA007C6CBE /* InfoPlist.strings in Resources */, 35C36F912259EAF4002FA5C6 /* Preferences.storyboard in Resources */, + 9A159CEB27B00E3300570529 /* Keys.plist in Resources */, 9AB89E031CE97A4900EC8EB1 /* Media.xcassets in Resources */, 9A13BAD61CA87F08007C6CBE /* Panel.xib in Resources */, 35C36F6B2259E0E1002FA5C6 /* FloatingWindow.xib in Resources */, @@ -1228,7 +1224,6 @@ 35C36F17225961DA002FA5C6 /* DateTools.bundle in Resources */, 35C36EF322595F14002FA5C6 /* WelcomeView.xib in Resources */, 3548C45A26BECF1B00AFB533 /* UpcomingEventViewItem.xib in Resources */, - 35DFBCF126A8479000D6648B /* Keys.plist in Resources */, 35C36EF822595F14002FA5C6 /* Onboarding.storyboard in Resources */, 35C36F612259DE67002FA5C6 /* NotesPopover.xib in Resources */, 9A3169C11D2CC5AA0079FDF8 /* com.abhishek.ClockerHelper.plist in Resources */, @@ -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 */, @@ -1578,7 +1572,7 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 98; + CURRENT_PROJECT_VERSION = 103; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1611,7 +1605,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 21.08.01; + MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.abhishek.Clocker; @@ -2157,7 +2151,7 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 98; + CURRENT_PROJECT_VERSION = 103; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -2191,7 +2185,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 21.08.01; + MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; @@ -2239,7 +2233,7 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 98; + CURRENT_PROJECT_VERSION = 103; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -2272,7 +2266,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 21.08.01; + MARKETING_VERSION = 22.03; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; "OTHER_SWIFT_FLAGS[arch=*]" = "-D RELEASE"; diff --git a/Clocker/Clocker/Utilities/CommonStrings.h b/Clocker/Clocker/Utilities/CommonStrings.h deleted file mode 100644 index baef60a..0000000 --- a/Clocker/Clocker/Utilities/CommonStrings.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2015 Abhishek Banthia - -#import - -@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 diff --git a/Clocker/Clocker/Utilities/CommonStrings.m b/Clocker/Clocker/Utilities/CommonStrings.m deleted file mode 100644 index f62f20c..0000000 --- a/Clocker/Clocker/Utilities/CommonStrings.m +++ /dev/null @@ -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 diff --git a/Clocker/ClockerUITests/ClockerUITests.m b/Clocker/ClockerUITests/ClockerUITests.m index c33a74c..4335207 100644 --- a/Clocker/ClockerUITests/ClockerUITests.m +++ b/Clocker/ClockerUITests/ClockerUITests.m @@ -1,7 +1,6 @@ // Copyright © 2015 Abhishek Banthia #import -#import "CommonStrings.h" @interface ClockerUITests : XCTestCase diff --git a/Clocker/ClockerUnitTests/ClockerUnitTests.swift b/Clocker/ClockerUnitTests/ClockerUnitTests.swift index 77dd55b..13cf265 100644 --- a/Clocker/ClockerUnitTests/ClockerUnitTests.swift +++ b/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 testDecoding() { + let timezone1 = TimezoneData.customObject(from: nil) + XCTAssertNotNil(timezone1) + + 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() { - let defaults = UserDefaults.standard - - guard var currentFavourites = defaults.object(forKey: CLDefaultPreferenceKey) as? [Data] else { - XCTFail("Default preferences aren't in the correct format") - return - } + 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() { diff --git a/Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift b/Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift index 081072a..f93625e 100644 --- a/Clocker/ClockerUnitTests/StandardMenubarHandlerTests.swift +++ b/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 diff --git a/Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift b/Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift index b58e3e1..0c0d6bf 100644 --- a/Clocker/CoreModelKit/Sources/CoreModelKit/TimezoneData.swift +++ b/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) """ diff --git a/Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift b/Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift index a143140..4da2c46 100644 --- a/Clocker/CoreModelKit/Tests/CoreModelKitTests/TimezoneDataEqualityTests.swift +++ b/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) } } diff --git a/Clocker/Overall App/AppDefaults.swift b/Clocker/Overall App/AppDefaults.swift index 85ed394..66fbdcd 100644 --- a/Clocker/Overall App/AppDefaults.swift +++ b/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") } diff --git a/Clocker/Overall App/DataStore.swift b/Clocker/Overall App/DataStore.swift index 1a51174..219ce12 100644 --- a/Clocker/Overall App/DataStore.swift +++ b/Clocker/Overall App/DataStore.swift @@ -45,12 +45,24 @@ 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 [] } 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 { @@ -75,10 +87,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) } @@ -86,10 +94,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() { @@ -103,7 +110,7 @@ class DataStore: NSObject { Logger.log(object: [:], for: "Undo Action Executed during Onboarding") - userDefaults.set(currentLineup, forKey: CLDefaultPreferenceKey) + setTimezones(currentLineup) } func timezoneFormat() -> NSNumber { diff --git a/Clocker/Panel/ParentPanelController.swift b/Clocker/Panel/ParentPanelController.swift index 8076ab2..c688d9f 100644 --- a/Clocker/Panel/ParentPanelController.swift +++ b/Clocker/Panel/ParentPanelController.swift @@ -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() @@ -241,6 +246,14 @@ class ParentPanelController: NSWindowController { coordinates: nil) } } + + // 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() diff --git a/Clocker/Preferences/Appearance/AppearanceViewController.swift b/Clocker/Preferences/Appearance/AppearanceViewController.swift index b1d762e..8294fe1 100644 --- a/Clocker/Preferences/Appearance/AppearanceViewController.swift +++ b/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() } diff --git a/Clocker/Preferences/Menu Bar/StatusContainerView.swift b/Clocker/Preferences/Menu Bar/StatusContainerView.swift index a7d12b9..66808f0 100644 --- a/Clocker/Preferences/Menu Bar/StatusContainerView.swift +++ b/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 + }) {} } } } diff --git a/Clocker/Preferences/Menu Bar/StatusItemHandler.swift b/Clocker/Preferences/Menu Bar/StatusItemHandler.swift index ca9dbf8..4c52168 100644 --- a/Clocker/Preferences/Menu Bar/StatusItemHandler.swift +++ b/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) {