diff --git a/Clocker/Clocker.xcodeproj/project.pbxproj b/Clocker/Clocker.xcodeproj/project.pbxproj index 2ebbcb7..d8f2ed0 100755 --- a/Clocker/Clocker.xcodeproj/project.pbxproj +++ b/Clocker/Clocker.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3595FAD0227F88BC0044A12A /* UserDefaults + KVOExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */; }; + 35C11E2124873A550031F18C /* VersionUpdateHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */; }; 35C36EE422595EFD002FA5C6 /* StatusContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE022595EFD002FA5C6 /* StatusContainerView.swift */; }; 35C36EE522595EFD002FA5C6 /* MenubarHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE122595EFD002FA5C6 /* MenubarHandler.swift */; }; 35C36EE622595EFD002FA5C6 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36EE222595EFD002FA5C6 /* StatusItemView.swift */; }; @@ -223,6 +224,7 @@ 352AF499232E07B400D96FA7 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; 3545C52A22612BCC00121E25 /* RateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateTests.swift; sourceTree = ""; }; 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults + KVOExtensions.swift"; sourceTree = ""; }; + 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateHandler.swift; sourceTree = ""; }; 35C36EE022595EFD002FA5C6 /* StatusContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusContainerView.swift; sourceTree = ""; }; 35C36EE122595EFD002FA5C6 /* MenubarHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenubarHandler.swift; sourceTree = ""; }; 35C36EE222595EFD002FA5C6 /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = ""; }; @@ -549,6 +551,7 @@ 35C36F392259D892002FA5C6 /* Themer.swift */, 35C36F3A2259D892002FA5C6 /* Timer.swift */, 3595FACF227F88BC0044A12A /* UserDefaults + KVOExtensions.swift */, + 35C11E2024873A550031F18C /* VersionUpdateHandler.swift */, ); path = "Overall App"; sourceTree = ""; @@ -1261,6 +1264,7 @@ 35C36F662259DF4C002FA5C6 /* ReviewView.swift in Sources */, 35C36EF522595F14002FA5C6 /* OnboardingSearchController.swift in Sources */, 35C36F592259DD8A002FA5C6 /* TimezoneCellView.swift in Sources */, + 35C11E2124873A550031F18C /* VersionUpdateHandler.swift in Sources */, 35C36F15225961DA002FA5C6 /* TimeChunk.swift in Sources */, 35C36F482259D892002FA5C6 /* NetworkManager.swift in Sources */, 9A8605AE1BEC148400A810A4 /* main.m in Sources */, diff --git a/Clocker/Overall App/VersionUpdateHandler.swift b/Clocker/Overall App/VersionUpdateHandler.swift new file mode 100644 index 0000000..b3e982f --- /dev/null +++ b/Clocker/Overall App/VersionUpdateHandler.swift @@ -0,0 +1,158 @@ +// Copyright © 2015 Abhishek Banthia + +import Cocoa + +class VersionUpdateHandler: NSObject { + enum VersionUpdateHandlerPriority { + case defaultPri + case low + case medium + case high + } + + static let kSecondsInDay: Double = 86400.0 + static let kMacAppStoreRefreshDelay: Double = 5.0 + static let kMacRequestTimeout: Double = 60.0 + static let kVersionCheckLastVersionKey = "VersionCheckLastVersionKey" + static let kVersionIgnoreVersionKey = "VersionCheckIgnoreVersionKey" + + private var appStoreCountry: String! + private var applicationVersion: String! + private var applicationBundleID: String = Bundle.main.bundleIdentifier ?? "N/A" + private var updatePriority: VersionUpdateHandlerPriority = VersionUpdateHandlerPriority.defaultPri + private var useAllAvailableLanguages: Bool = true + private var onlyPromptIfMainWindowIsAvailable: Bool = true + private var checkAtLaunch: Bool = true + private var checkPeriod: Float = 0.0 + private var remindPeriod: Float = 1.0 + private var verboseLogging: Bool = true + + private var showOnFirstLaunch: Bool = false + public var previewMode: Bool = false + private var versionDetails: String? + + override init() { + // Setup App Store Country + appStoreCountry = Locale.current.regionCode + if appStoreCountry == "150" { + appStoreCountry = "eu" + } else if appStoreCountry.replacingOccurrences(of: "[A-Za-z]{2}", with: "", options: .regularExpression, range: appStoreCountry.startIndex ..< appStoreCountry.endIndex).count > 0 { + appStoreCountry = "us" + } + + // Setup App Version + var appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + if appVersion == nil { + appVersion = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String + } + + applicationVersion = appVersion ?? "N/A" + + // Bundle Identifier + + super.init() + } + + private func shouldCheckForNewVersion() -> Bool { + return true + } + + private func applicationLaunched() { + if checkAtLaunch { + checkIfNewVersion() + } else if verboseLogging { + print("iVersion will not check for updatess because checkAtLaunch option is disabled") + } + } + + private func lastVersion() -> String { + return UserDefaults.standard.object(forKey: VersionUpdateHandler.kVersionCheckLastVersionKey) as? String ?? "" + } + + private func setLastReminded(_ date: Date?) { + UserDefaults.standard.set(date, forKey: VersionUpdateHandler.kVersionIgnoreVersionKey) + } + + private func versionDetails(_ version: String, _ dict: [String: Any]) -> String? { + if let versionData = dict[version] as? String { + return versionData + } else if let versionDataArray = dict[version] as? NSArray { + return versionDataArray.componentsJoined(by: "\n") + } + return nil + } + + private func setViewedVersionDetails(_ viewed: Bool) { + UserDefaults.standard.set(viewed ? applicationVersion : nil, forKey: VersionUpdateHandler.kVersionCheckLastVersionKey) + } + + private func viewedVersionDetails() -> Bool { + let lastVersionKey = UserDefaults.standard.object(forKey: VersionUpdateHandler.kVersionCheckLastVersionKey) as? String ?? "" + return lastVersionKey == applicationVersion + } + + private func localVersionsDict() -> [String: Any] { + return [String: Any]() + } + + private func versionDetailsString() -> String { + if versionDetails == nil { + if viewedVersionDetails() { + versionDetails = versionDetails(applicationVersion, localVersionsDict()) + } + } else { + versionDetails = versionDetails(lastVersion(), localVersionsDict()) + } + + return versionDetails! + } + + private func checkIfNewVersion() { + if onlyPromptIfMainWindowIsAvailable { + guard NSApplication.shared.mainWindow != nil else { + return + } + + _ = Repeater(interval: .seconds(5), mode: .infinite) { _ in + OperationQueue.main.addOperation { [weak self] in + guard let self = self else { + return + } + self.checkIfNewVersion() + } + } + } + + let lastVersionString = lastVersion() + if lastVersionString.count > 0 || showOnFirstLaunch || previewMode { + if applicationVersion.compareVersion(lastVersionString) == ComparisonResult.orderedDescending || previewMode { + // Clear Reminder + setLastReminded(nil) + } + } + } +} + +extension String { + func compareVersion(_ version: String) -> ComparisonResult { + return compare(version, + options: CompareOptions.numeric, + range: nil, + locale: nil) + } + + func compareVersionDescending(_ version: String) -> ComparisonResult { + let comparsionResult = (0 - compareVersion(version).rawValue) + switch comparsionResult { + case -1: + return ComparisonResult.orderedAscending + case 0: + return ComparisonResult.orderedSame + case 1: + return ComparisonResult.orderedDescending + default: + assertionFailure("Invalid Comparison Result") + return .orderedSame + } + } +}