//
// C o p y r i g h t ( c ) 2 0 1 9 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
import Foundation
import PromiseKit
@objc
class AppUpdateNag : NSObject {
// MARK: P u b l i c
@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: - I n t e r n a l
let kUpgradeNagCollection = " TSStorageManagerAppUpgradeNagCollection "
let kLastNagDateKey = " TSStorageManagerAppUpgradeNagDate "
let kFirstHeardOfNewVersionDateKey = " TSStorageManagerAppUpgradeFirstHeardOfNewVersionDate "
var dbConnection : YapDatabaseConnection {
return OWSPrimaryStorage . shared ( ) . dbReadWriteConnection
}
// MARK: B u n d l e a c c e s s o r s
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
}
}
// O n l y s h o w n a g i f w e a r e " a t r e s t " i n t h e h o m e v i e w o r r e g i s t r a t i o n v i e w w i t h o u t a n y
// a l e r t s o r d i a l o g s s h o w i n g .
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: S t o r a g e
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: P a r s i n g S t r u c t s
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 < AppStoreRecord > {
Logger . debug ( " lookupURL: \( lookupURL ) " )
let ( promise , resolver ) = Promise < AppStoreRecord > . 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 )
}
}