diff --git a/LoopFollow/Application/AppDelegate.swift b/LoopFollow/Application/AppDelegate.swift index fc460a53..bece3e25 100644 --- a/LoopFollow/Application/AppDelegate.swift +++ b/LoopFollow/Application/AppDelegate.swift @@ -42,7 +42,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - func applicationWillTerminate(_ application: UIApplication) { if UserDefaultsRepository.alertAppInactive.value { AlarmSound.setSoundFile(str: "Alarm_Buzzer") diff --git a/LoopFollow/ViewControllers/MainViewController.swift b/LoopFollow/ViewControllers/MainViewController.swift index f2e0d177..41032e41 100644 --- a/LoopFollow/ViewControllers/MainViewController.swift +++ b/LoopFollow/ViewControllers/MainViewController.swift @@ -423,9 +423,63 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele } restartAllTimers() - + checkAndNotifyVersionStatus() + checkAppExpirationStatus() } + func checkAndNotifyVersionStatus() { + let versionManager = AppVersionManager() + versionManager.checkForNewVersion { latestVersion, isNewer, isBlacklisted in + let now = Date() + + // Check if the current version is blacklisted, or if there is a newer version available + if isBlacklisted { + let lastBlacklistShown = UserDefaultsRepository.lastBlacklistNotificationShown.value ?? Date.distantPast + if now.timeIntervalSince(lastBlacklistShown) > 86400 { // 24 hours + self.versionAlert(message: "The current version has a critical issue and should be updated as soon as possible.") + UserDefaultsRepository.lastBlacklistNotificationShown.value = now + UserDefaultsRepository.lastVersionUpdateNotificationShown.value = now + } + } else if isNewer { + let lastVersionUpdateShown = UserDefaultsRepository.lastVersionUpdateNotificationShown.value ?? Date.distantPast + if now.timeIntervalSince(lastVersionUpdateShown) > 1209600 { // 2 weeks + self.versionAlert(message: "A new version is available: \(latestVersion ?? "Unknown"). It is recommended to update.") + UserDefaultsRepository.lastVersionUpdateNotificationShown.value = now + } + } + } + } + + func versionAlert(title: String = "Update Available", message: String) { + DispatchQueue.main.async { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true) + } + } + + func checkAppExpirationStatus() { + let now = Date() + let expirationDate = BuildDetails.default.calculateExpirationDate() + let weekBeforeExpiration = Calendar.current.date(byAdding: .day, value: -7, to: expirationDate)! + + if now >= weekBeforeExpiration { + let lastExpirationShown = UserDefaultsRepository.lastExpirationNotificationShown.value ?? Date.distantPast + if now.timeIntervalSince(lastExpirationShown) > 86400 { // 24 hours + expirationAlert() + UserDefaultsRepository.lastExpirationNotificationShown.value = now + } + } + } + + func expirationAlert() { + DispatchQueue.main.async { + let alert = UIAlertController(title: "App Expiration Warning", message: "This app will expire in less than a week. Please rebuild to continue using it.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true) + } + } + @objc override func viewDidAppear(_ animated: Bool) { showHideNSDetails() } diff --git a/LoopFollow/helpers/AppVersionManager.swift b/LoopFollow/helpers/AppVersionManager.swift index c35dcb06..02cdeba6 100644 --- a/LoopFollow/helpers/AppVersionManager.swift +++ b/LoopFollow/helpers/AppVersionManager.swift @@ -11,23 +11,38 @@ import Foundation class AppVersionManager { private let githubService = GitHubService() + /// Checks for the availability of a new app version and if the current version is blacklisted. + /// - Parameter completion: Returns latest version, a boolean for newer version existence, and blacklist status. + /// Usage: `versionManager.checkForNewVersion { latestVersion, isNewer, isBlacklisted in ... }` func checkForNewVersion(completion: @escaping (String?, Bool, Bool) -> Void) { let currentVersion = version() let now = Date() - - // Retrieve cache - let lastChecked = UserDefaults.standard.object(forKey: "latestVersionChecked") as? Date ?? Date.distantPast - let cachedLatestVersion = UserDefaults.standard.string(forKey: "latestVersion") - let isBlacklistedCached = UserDefaults.standard.bool(forKey: "isCurrentVersionBlacklisted") + // Retrieve cache + let latestVersionChecked = UserDefaultsRepository.latestVersionChecked.value ?? Date.distantPast + let latestVersion = UserDefaultsRepository.latestVersion.value + let currentVersionBlackListed = UserDefaultsRepository.currentVersionBlackListed.value + let cachedForVersion = UserDefaultsRepository.cachedForVersion.value + + // Reset notifications if version has changed + if let cachedVersion = cachedForVersion, cachedVersion != currentVersion { + UserDefaultsRepository.lastBlacklistNotificationShown.value = Date.distantPast + UserDefaultsRepository.lastVersionUpdateNotificationShown.value = Date.distantPast + } + // Check if the cache is still valid - if now.timeIntervalSince(lastChecked) < 24 * 3600, let latestVersion = cachedLatestVersion { + if let cachedVersion = cachedForVersion, cachedVersion == currentVersion, + now.timeIntervalSince(latestVersionChecked) < 24 * 3600, let latestVersion = latestVersion { let isNewer = isVersion(latestVersion, newerThan: currentVersion) - completion(latestVersion, isNewer, isBlacklistedCached) + completion(latestVersion, isNewer, currentVersionBlackListed) return } - - // Fetch new data if cache is outdated + + // Fetch new data if cache is outdated or not for current version + fetchDataAndUpdateCache(currentVersion: currentVersion, completion: completion) + } + + private func fetchDataAndUpdateCache(currentVersion: String, completion: @escaping (String?, Bool, Bool) -> Void) { githubService.fetchData(for: .versionConfig) { versionData in self.githubService.fetchData(for: .blacklistedVersions) { blacklistData in DispatchQueue.main.async { @@ -39,9 +54,10 @@ class AppVersionManager { .map { $0.blacklistedVersions.map { $0.version }.contains(currentVersion) } ?? false // Update cache with new data - UserDefaults.standard.set(fetchedVersion, forKey: "latestVersion") - UserDefaults.standard.set(Date(), forKey: "latestVersionChecked") - UserDefaults.standard.set(isBlacklisted, forKey: "isCurrentVersionBlacklisted") + UserDefaultsRepository.latestVersion.value = fetchedVersion + UserDefaultsRepository.latestVersionChecked.value = Date() + UserDefaultsRepository.currentVersionBlackListed.value = isBlacklisted + UserDefaultsRepository.cachedForVersion.value = currentVersion // Call completion with new data completion(fetchedVersion, isNewer, isBlacklisted) @@ -62,7 +78,7 @@ class AppVersionManager { } return nil } - + private func isVersion(_ fetchedVersion: String, newerThan currentVersion: String) -> Bool { let fetchedVersionComponents = fetchedVersion.split(separator: ".").map { Int($0) ?? 0 } let currentVersionComponents = currentVersion.split(separator: ".").map { Int($0) ?? 0 } @@ -79,18 +95,18 @@ class AppVersionManager { } return false } - + func version() -> String { if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { return version } return "Unknown" } - + struct Blacklist: Decodable { let blacklistedVersions: [VersionEntry] } - + struct VersionEntry: Decodable { let version: String } diff --git a/LoopFollow/helpers/BuildDetails.swift b/LoopFollow/helpers/BuildDetails.swift index 68c31e68..6afc994f 100644 --- a/LoopFollow/helpers/BuildDetails.swift +++ b/LoopFollow/helpers/BuildDetails.swift @@ -71,7 +71,7 @@ class BuildDetails { if let provision = MobileProvision.read() { return provision.expirationDate } else { - return Date() + return .distantFuture } } } diff --git a/LoopFollow/repository/UserDefaults.swift b/LoopFollow/repository/UserDefaults.swift index 1f18e377..c2a16fcf 100644 --- a/LoopFollow/repository/UserDefaults.swift +++ b/LoopFollow/repository/UserDefaults.swift @@ -460,10 +460,20 @@ class UserDefaultsRepository { static let alertRecBolusSnoozedTime = UserDefaultsValue(key: "alertRecBolusSnoozedTime", default: nil) static var deviceRecBolus: UserDefaultsValue = UserDefaultsValue(key: "deviceRecBolus", default: 0.0) + //What version is the cache valid for + static let cachedForVersion = UserDefaultsValue(key: "cachedForVersion", default: nil) + //Caching of latest version static let latestVersion = UserDefaultsValue(key: "latestVersion", default: nil) static let latestVersionChecked = UserDefaultsValue(key: "latestVersionChecked", default: nil) //Caching of blacklisted version static let currentVersionBlackListed = UserDefaultsValue(key: "currentVersionBlackListed", default: false) + + // Tracking notifications to manage frequency + static let lastBlacklistNotificationShown = UserDefaultsValue(key: "lastBlacklistNotificationShown", default: nil) + static let lastVersionUpdateNotificationShown = UserDefaultsValue(key: "lastVersionUpdateNotificationShown", default: nil) + + // Tracking the last time the expiration notification was shown + static let lastExpirationNotificationShown = UserDefaultsValue(key: "lastExpirationNotificationShown", default: nil) }