|
|
// Copyright © 2015 Abhishek Banthia |
|
|
|
|
|
import Cocoa |
|
|
import CoreLoggerKit |
|
|
import CoreModelKit |
|
|
|
|
|
enum ViewType { |
|
|
case futureSlider |
|
|
case upcomingEventView |
|
|
case twelveHour |
|
|
case sunrise |
|
|
case showMeetingInMenubar |
|
|
case showAllDayEventsInMenubar |
|
|
case showAppInForeground |
|
|
case appDisplayOptions |
|
|
case dateInMenubar |
|
|
case placeInMenubar |
|
|
case dayInMenubar |
|
|
case menubarCompactMode |
|
|
case sync |
|
|
} |
|
|
|
|
|
class DataStore: NSObject { |
|
|
private static var sharedStore = DataStore(with: UserDefaults.standard) |
|
|
private var userDefaults: UserDefaults! |
|
|
private var ubiquitousStore: NSUbiquitousKeyValueStore? |
|
|
private var cachedTimezones: [Data] |
|
|
private var cachedMenubarTimezones: [Data] |
|
|
private static let timeFormatsWithSuffix: Set<NSNumber> = Set([NSNumber(integerLiteral: 0), |
|
|
NSNumber(integerLiteral: 3), |
|
|
NSNumber(integerLiteral: 4), |
|
|
NSNumber(integerLiteral: 6), |
|
|
NSNumber(integerLiteral: 7)]) |
|
|
|
|
|
class func shared() -> DataStore { |
|
|
return sharedStore |
|
|
} |
|
|
|
|
|
init(with defaults: UserDefaults) { |
|
|
cachedTimezones = (defaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data]) ?? [] |
|
|
cachedMenubarTimezones = cachedTimezones.filter { |
|
|
let customTimezone = TimezoneData.customObject(from: $0) |
|
|
return customTimezone?.isFavourite == 1 |
|
|
} |
|
|
userDefaults = defaults |
|
|
super.init() |
|
|
|
|
|
setupSyncNotification() |
|
|
} |
|
|
|
|
|
func setupSyncNotification() { |
|
|
if shouldDisplay(.sync) { |
|
|
ubiquitousStore = NSUbiquitousKeyValueStore.default |
|
|
NotificationCenter.default.addObserver(self, |
|
|
selector: #selector(ubiquitousKeyValueStoreChanged), |
|
|
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, |
|
|
object: NSUbiquitousKeyValueStore.default) |
|
|
let synchronizationResult = ubiquitousStore?.synchronize() ?? false |
|
|
let resultString = synchronizationResult ? "successfully" : "unsuccessfully" |
|
|
Logger.info("Ubiquitous Store synchronized \(resultString)") |
|
|
} else { |
|
|
NotificationCenter.default.removeObserver(self, |
|
|
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, |
|
|
object: nil) |
|
|
} |
|
|
} |
|
|
|
|
|
@objc func ubiquitousKeyValueStoreChanged(_ notification: Notification) { |
|
|
let userInfo = notification.userInfo ?? [:] |
|
|
let ubiquitousStore = notification.object as? NSUbiquitousKeyValueStore |
|
|
Logger.info("Ubiquitous Store Changed: User Info is \(userInfo)") |
|
|
let currentTimezones = userDefaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data] |
|
|
let cloudTimezones = ubiquitousStore?.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data] |
|
|
let cloudLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey) as? Date) ?? Date() |
|
|
let defaultsLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.userDefaultsLastUpdateKey) as? Date) ?? Date() |
|
|
|
|
|
if cloudTimezones == currentTimezones { |
|
|
Logger.info("Ubiquitous Store timezones aren't equal to current timezones") |
|
|
} |
|
|
|
|
|
if defaultsLastUpdateDate.isLaterThanOrEqual(to: cloudLastUpdateDate) { |
|
|
Logger.info("Ubiquitous Store is stale as compared to User Defaults") |
|
|
} |
|
|
|
|
|
if cloudTimezones != currentTimezones, cloudLastUpdateDate.isLaterThanOrEqual(to: defaultsLastUpdateDate) { |
|
|
Logger.info("Syncing local timezones with data from the ☁️. ☁️ last update timestamp is recent") |
|
|
userDefaults.set(cloudTimezones, forKey: UserDefaultKeys.defaultPreferenceKey) |
|
|
userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey) |
|
|
NotificationCenter.default.post(name: DataStore.didSyncFromExternalSourceNotification, |
|
|
object: self) |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
func timezones() -> [Data] { |
|
|
return cachedTimezones |
|
|
} |
|
|
|
|
|
func setTimezones(_ timezones: [Data]?) { |
|
|
userDefaults.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey) |
|
|
userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey) |
|
|
cachedTimezones = timezones ?? [] |
|
|
cachedMenubarTimezones = cachedTimezones.filter { |
|
|
let customTimezone = TimezoneData.customObject(from: $0) |
|
|
return customTimezone?.isFavourite == 1 |
|
|
} |
|
|
// iCloud sync |
|
|
ubiquitousStore?.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey) |
|
|
ubiquitousStore?.set(Date(), forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey) |
|
|
} |
|
|
|
|
|
func menubarTimezones() -> [Data]? { |
|
|
return cachedMenubarTimezones |
|
|
} |
|
|
|
|
|
func selectedCalendars() -> [String]? { |
|
|
return userDefaults.array(forKey: UserDefaultKeys.selectedCalendars) as? [String] |
|
|
} |
|
|
|
|
|
// MARK: Date (May 8th) in Compact Menubar |
|
|
|
|
|
func shouldShowDateInMenubar() -> Bool { |
|
|
return shouldDisplay(.dateInMenubar) |
|
|
} |
|
|
|
|
|
// MARK: Day (Sun, Mon etc.) in Compact Menubar |
|
|
|
|
|
func shouldShowDayInMenubar() -> Bool { |
|
|
return shouldDisplay(.dayInMenubar) |
|
|
} |
|
|
|
|
|
func retrieve(key: String) -> Any? { |
|
|
return userDefaults.object(forKey: key) |
|
|
} |
|
|
|
|
|
func addTimezone(_ timezone: TimezoneData) { |
|
|
guard let encodedTimezone = NSKeyedArchiver.clocker_archive(with: timezone) else { |
|
|
return |
|
|
} |
|
|
|
|
|
var defaults: [Data] = timezones() |
|
|
defaults.append(encodedTimezone) |
|
|
setTimezones(defaults) |
|
|
} |
|
|
|
|
|
func removeLastTimezone() { |
|
|
var currentLineup = timezones() |
|
|
|
|
|
if currentLineup.isEmpty { |
|
|
return |
|
|
} |
|
|
|
|
|
currentLineup.removeLast() |
|
|
|
|
|
Logger.log(object: [:], for: "Undo Action Executed during Onboarding") |
|
|
|
|
|
setTimezones(currentLineup) |
|
|
} |
|
|
|
|
|
func timezoneFormat() -> NSNumber { |
|
|
return userDefaults.object(forKey: UserDefaultKeys.selectedTimeZoneFormatKey) as? NSNumber ?? NSNumber(integerLiteral: 0) |
|
|
} |
|
|
|
|
|
func isBufferRequiredForTwelveHourFormats() -> Bool { |
|
|
return DataStore.timeFormatsWithSuffix.contains(timezoneFormat()) |
|
|
} |
|
|
|
|
|
func shouldDisplay(_ type: ViewType) -> Bool { |
|
|
switch type { |
|
|
case .futureSlider: |
|
|
guard let value = retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber else { |
|
|
return false |
|
|
} |
|
|
return value != 1 // Display slider is 0 and Hide is 1. |
|
|
case .upcomingEventView: |
|
|
guard let value = retrieve(key: UserDefaultKeys.showUpcomingEventView) as? NSString else { |
|
|
return false |
|
|
} |
|
|
return value == "YES" |
|
|
case .twelveHour: |
|
|
return shouldDisplayHelper(UserDefaultKeys.selectedTimeZoneFormatKey) |
|
|
case .showAllDayEventsInMenubar: |
|
|
return shouldDisplayHelper(UserDefaultKeys.showAllDayEventsInUpcomingView) |
|
|
case .sunrise: |
|
|
return shouldDisplayHelper(UserDefaultKeys.sunriseSunsetTime) |
|
|
case .showMeetingInMenubar: |
|
|
return shouldDisplayHelper(UserDefaultKeys.showMeetingInMenubar) |
|
|
case .showAppInForeground: |
|
|
guard let value = retrieve(key: UserDefaultKeys.showAppInForeground) as? NSNumber else { |
|
|
return false |
|
|
} |
|
|
return value.isEqual(to: NSNumber(value: 1)) |
|
|
case .dateInMenubar: |
|
|
return shouldDisplayNonObjectHelper(UserDefaultKeys.showDateInMenu) |
|
|
case .placeInMenubar: |
|
|
return shouldDisplayHelper(UserDefaultKeys.showPlaceInMenu) |
|
|
case .dayInMenubar: |
|
|
return shouldDisplayNonObjectHelper(UserDefaultKeys.showDayInMenu) |
|
|
case .appDisplayOptions: |
|
|
return shouldDisplayHelper(UserDefaultKeys.appDisplayOptions) |
|
|
case .menubarCompactMode: |
|
|
guard let value = retrieve(key: UserDefaultKeys.menubarCompactMode) as? Int else { |
|
|
return false |
|
|
} |
|
|
|
|
|
return value == 0 |
|
|
case .sync: |
|
|
return shouldDisplayHelper(UserDefaultKeys.enableSyncKey) |
|
|
} |
|
|
} |
|
|
|
|
|
// MARK: Private |
|
|
|
|
|
private func shouldDisplayHelper(_ key: String) -> Bool { |
|
|
guard let value = retrieve(key: key) as? NSNumber else { |
|
|
return false |
|
|
} |
|
|
return value.isEqual(to: NSNumber(value: 0)) |
|
|
} |
|
|
|
|
|
// MARK: Some values are stored as plain integers; objectForKey: will return nil, so using integerForKey: |
|
|
|
|
|
private func shouldDisplayNonObjectHelper(_ key: String) -> Bool { |
|
|
let value = userDefaults.integer(forKey: key) |
|
|
return value == 0 |
|
|
} |
|
|
} |
|
|
|
|
|
extension DataStore { |
|
|
public static let didSyncFromExternalSourceNotification: NSNotification.Name = .init("didSyncFromExternalSourceNotification") |
|
|
}
|
|
|
|