// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // import Foundation import PromiseKit @objc class AppUpdateNag: NSObject { // MARK: Public @objc(sharedInstance) public static let shared: AppUpdateNag = { let versionService = AppStoreVersionService() let nagManager = AppUpdateNag(versionService: versionService) return nagManager }() @objc public func showAppUpgradeNagIfNecessary() { return /* guard let currentVersion = self.currentVersion else { owsFailDebug("currentVersion was unexpectedly nil") return } guard let bundleIdentifier = self.bundleIdentifier else { owsFailDebug("bundleIdentifier was unexpectedly nil") return } guard let lookupURL = lookupURL(bundleIdentifier: bundleIdentifier) else { owsFailDebug("appStoreURL was unexpectedly nil") return } firstly { self.versionService.fetchLatestVersion(lookupURL: lookupURL) }.done { appStoreRecord in guard appStoreRecord.version.compare(currentVersion, options: .numeric) == ComparisonResult.orderedDescending else { Logger.debug("remote version: \(appStoreRecord) is not newer than currentVersion: \(currentVersion)") return } Logger.info("new version available: \(appStoreRecord)") self.showUpdateNagIfEnoughTimeHasPassed(appStoreRecord: appStoreRecord) }.catch { error in Logger.error("failed with error: \(error)") }.retainUntilComplete() */ } // MARK: - Internal let kUpgradeNagCollection = "TSStorageManagerAppUpgradeNagCollection" let kLastNagDateKey = "TSStorageManagerAppUpgradeNagDate" let kFirstHeardOfNewVersionDateKey = "TSStorageManagerAppUpgradeFirstHeardOfNewVersionDate" var dbConnection: YapDatabaseConnection { return OWSPrimaryStorage.shared().dbReadWriteConnection } // MARK: Bundle accessors var bundle: Bundle { return Bundle.main } var currentVersion: String? { return bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String } var bundleIdentifier: String? { return bundle.bundleIdentifier } func lookupURL(bundleIdentifier: String) -> URL? { return URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleIdentifier)") } let versionService: AppStoreVersionService required init(versionService: AppStoreVersionService) { self.versionService = versionService super.init() SwiftSingletons.register(self) } func showUpdateNagIfEnoughTimeHasPassed(appStoreRecord: AppStoreRecord) { guard let firstHeardOfNewVersionDate = self.firstHeardOfNewVersionDate else { self.setFirstHeardOfNewVersionDate(Date()) return } let intervalBeforeNag = 7 * kDayInterval guard Date() > Date.init(timeInterval: intervalBeforeNag, since: firstHeardOfNewVersionDate) else { Logger.info("firstHeardOfNewVersionDate: \(firstHeardOfNewVersionDate) not nagging for new release yet.") return } if let lastNagDate = self.lastNagDate { let intervalBetweenNags = 14 * kDayInterval guard Date() > Date.init(timeInterval: intervalBetweenNags, since: lastNagDate) else { Logger.info("lastNagDate: \(lastNagDate) not nagging again so soon.") return } } // Only show nag if we are "at rest" in the home view or registration view without any // alerts or dialogs showing. guard UIApplication.shared.frontmostViewController != nil else { owsFailDebug("frontmostViewController was unexpectedly nil") return } /* switch frontmostViewController { case is OnboardingSplashViewController: self.setLastNagDate(Date()) self.clearFirstHeardOfNewVersionDate() presentUpgradeNag(appStoreRecord: appStoreRecord) default: Logger.debug("not presenting alert due to frontmostViewController: \(frontmostViewController)") break } */ } func presentUpgradeNag(appStoreRecord: AppStoreRecord) { let title = NSLocalizedString("APP_UPDATE_NAG_ALERT_TITLE", comment: "Title for the 'new app version available' alert.") let bodyFormat = NSLocalizedString("APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT", comment: "Message format for the 'new app version available' alert. Embeds: {{The latest app version number}}") let bodyText = String(format: bodyFormat, appStoreRecord.version) let updateButtonText = NSLocalizedString("APP_UPDATE_NAG_ALERT_UPDATE_BUTTON", comment: "Label for the 'update' button in the 'new app version available' alert.") let dismissButtonText = NSLocalizedString("APP_UPDATE_NAG_ALERT_DISMISS_BUTTON", comment: "Label for the 'dismiss' button in the 'new app version available' alert.") let alert = UIAlertController(title: title, message: bodyText, preferredStyle: .alert) let updateAction = UIAlertAction(title: updateButtonText, style: .default) { [weak self] _ in guard let strongSelf = self else { return } strongSelf.showAppStore(appStoreURL: appStoreRecord.appStoreURL) } alert.addAction(updateAction) alert.addAction(UIAlertAction(title: dismissButtonText, style: .cancel, handler: nil)) OWSAlerts.showAlert(alert) } func showAppStore(appStoreURL: URL) { Logger.debug("") UIApplication.shared.openURL(appStoreURL) } // MARK: Storage var firstHeardOfNewVersionDate: Date? { return self.dbConnection.date(forKey: kFirstHeardOfNewVersionDateKey, inCollection: kUpgradeNagCollection) } func setFirstHeardOfNewVersionDate(_ date: Date) { self.dbConnection.setDate(date, forKey: kFirstHeardOfNewVersionDateKey, inCollection: kUpgradeNagCollection) } func clearFirstHeardOfNewVersionDate() { self.dbConnection.removeObject(forKey: kFirstHeardOfNewVersionDateKey, inCollection: kUpgradeNagCollection) } var lastNagDate: Date? { return self.dbConnection.date(forKey: kLastNagDateKey, inCollection: kUpgradeNagCollection) } func setLastNagDate(_ date: Date) { self.dbConnection.setDate(date, forKey: kLastNagDateKey, inCollection: kUpgradeNagCollection) } } // MARK: Parsing Structs struct AppStoreLookupResultSet: Codable { let resultCount: UInt let results: [AppStoreRecord] } struct AppStoreRecord: Codable { let appStoreURL: URL let version: String private enum CodingKeys: String, CodingKey { case appStoreURL = "trackViewUrl" case version } } class AppStoreVersionService: NSObject { // MARK: func fetchLatestVersion(lookupURL: URL) -> Promise { Logger.debug("lookupURL:\(lookupURL)") let (promise, resolver) = Promise.pending() let task = URLSession.ephemeral.dataTask(with: lookupURL) { (data, _, error) in guard let data = data else { Logger.warn("data was unexpectedly nil") resolver.reject(OWSErrorMakeUnableToProcessServerResponseError()) return } do { let decoder = JSONDecoder() let resultSet = try decoder.decode(AppStoreLookupResultSet.self, from: data) guard let appStoreRecord = resultSet.results.first else { Logger.warn("record was unexpectedly nil") resolver.reject(OWSErrorMakeUnableToProcessServerResponseError()) return } resolver.fulfill(appStoreRecord) } catch { resolver.reject(error) } } task.resume() return promise } } extension URLSession { static var ephemeral: URLSession { return URLSession(configuration: .ephemeral) } }