mirror of https://github.com/oxen-io/session-ios
Wait a week before nagging when a new release comes out
parent
63c94efc86
commit
9662b3cb1e
@ -1 +1 @@
|
|||||||
Subproject commit 5da11dee08c2cc8864ea03b1489bd8898d28dd4e
|
Subproject commit 73bf1779e0298cbc28c0b93d908bae0aa1f44bbc
|
@ -1,13 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
@interface AppUpdateNag : NSObject
|
|
||||||
|
|
||||||
- (instancetype)init NS_UNAVAILABLE;
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance;
|
|
||||||
|
|
||||||
- (void)showAppUpgradeNagIfNecessary;
|
|
||||||
|
|
||||||
@end
|
|
@ -1,118 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "AppUpdateNag.h"
|
|
||||||
#import "RegistrationViewController.h"
|
|
||||||
#import "Signal-Swift.h"
|
|
||||||
#import <ATAppUpdater/ATAppUpdater.h>
|
|
||||||
#import <SignalServiceKit/NSDate+OWS.h>
|
|
||||||
#import <SignalServiceKit/OWSPrimaryStorage.h>
|
|
||||||
|
|
||||||
NSString *const OWSPrimaryStorageAppUpgradeNagCollection = @"TSStorageManagerAppUpgradeNagCollection";
|
|
||||||
NSString *const OWSPrimaryStorageAppUpgradeNagDate = @"TSStorageManagerAppUpgradeNagDate";
|
|
||||||
|
|
||||||
@interface AppUpdateNag () <ATAppUpdaterDelegate>
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
@implementation AppUpdateNag
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance
|
|
||||||
{
|
|
||||||
static AppUpdateNag *sharedInstance = nil;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
sharedInstance = [[self alloc] initDefault];
|
|
||||||
});
|
|
||||||
return sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initDefault
|
|
||||||
{
|
|
||||||
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
|
|
||||||
|
|
||||||
return [self initWithPrimaryStorage:primaryStorage];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
|
|
||||||
if (!self) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
OWSAssert(primaryStorage);
|
|
||||||
|
|
||||||
_dbConnection = primaryStorage.newDatabaseConnection;
|
|
||||||
|
|
||||||
OWSSingletonAssert();
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)showAppUpgradeNagIfNecessary
|
|
||||||
{
|
|
||||||
if (CurrentAppContext().isRunningTests) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only show nag if we are "at rest" in the home view or registration view without any
|
|
||||||
// alerts or dialogs showing.
|
|
||||||
UIViewController *frontmostViewController =
|
|
||||||
[UIApplication sharedApplication].frontmostViewController;
|
|
||||||
OWSAssert(frontmostViewController);
|
|
||||||
BOOL canPresent = ([frontmostViewController isKindOfClass:[HomeViewController class]] ||
|
|
||||||
[frontmostViewController isKindOfClass:[RegistrationViewController class]]);
|
|
||||||
if (!canPresent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSDate *lastNagDate = [self.dbConnection dateForKey:OWSPrimaryStorageAppUpgradeNagDate
|
|
||||||
inCollection:OWSPrimaryStorageAppUpgradeNagCollection];
|
|
||||||
const NSTimeInterval kNagFrequency = kDayInterval * 14;
|
|
||||||
BOOL canNag = (!lastNagDate || fabs(lastNagDate.timeIntervalSinceNow) > kNagFrequency);
|
|
||||||
if (!canNag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ATAppUpdater *updater = [ATAppUpdater sharedUpdater];
|
|
||||||
[updater setAlertTitle:NSLocalizedString(
|
|
||||||
@"APP_UPDATE_NAG_ALERT_TITLE", @"Title for the 'new app version available' alert.")];
|
|
||||||
[updater setAlertMessage:NSLocalizedString(@"APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT",
|
|
||||||
@"Message format for the 'new app version available' alert. Embeds: {{The latest app "
|
|
||||||
@"version number.}}.")];
|
|
||||||
[updater setAlertUpdateButtonTitle:NSLocalizedString(@"APP_UPDATE_NAG_ALERT_UPDATE_BUTTON",
|
|
||||||
@"Label for the 'update' button in the 'new app version available' alert.")];
|
|
||||||
[updater setAlertCancelButtonTitle:CommonStrings.cancelButton];
|
|
||||||
[updater setDelegate:self];
|
|
||||||
[updater showUpdateWithConfirmation];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - ATAppUpdaterDelegate
|
|
||||||
|
|
||||||
- (void)appUpdaterDidShowUpdateDialog
|
|
||||||
{
|
|
||||||
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
||||||
|
|
||||||
[self.dbConnection setDate:[NSDate new]
|
|
||||||
forKey:OWSPrimaryStorageAppUpgradeNagDate
|
|
||||||
inCollection:OWSPrimaryStorageAppUpgradeNagCollection];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)appUpdaterUserDidLaunchAppStore
|
|
||||||
{
|
|
||||||
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)appUpdaterUserDidCancel
|
|
||||||
{
|
|
||||||
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -0,0 +1,233 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 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() {
|
||||||
|
|
||||||
|
guard let currentVersion = self.currentVersion else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) currentVersion was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let bundleIdentifier = self.bundleIdentifier else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) bundleIdentifier was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let lookupURL = lookupURL(bundleIdentifier: bundleIdentifier) else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) appStoreURL was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
firstly {
|
||||||
|
self.versionService.fetchLatestVersion(lookupURL: lookupURL)
|
||||||
|
}.then { appStoreRecord -> Void in
|
||||||
|
guard appStoreRecord.version.compare(currentVersion, options: .numeric) == ComparisonResult.orderedDescending else {
|
||||||
|
Logger.debug("\(self.logTag) same old version: \(appStoreRecord)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info("\(self.logTag) new version available: \(appStoreRecord)")
|
||||||
|
self.showUpdateNagIfEnoughTimeHasPassed(appStoreRecord: appStoreRecord)
|
||||||
|
}.catch { error in
|
||||||
|
Logger.error("\(self.logTag) in \(#function) 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("\(logTag) in \(#function) 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("\(logTag) in \(#function) 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 let frontmostViewController = UIApplication.shared.frontmostViewController else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) frontmostViewController was unexpectedly nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch frontmostViewController {
|
||||||
|
case is HomeViewController, is RegistrationViewController:
|
||||||
|
self.setLastNagDate(Date())
|
||||||
|
self.clearFirstHeardOfNewVersionDate()
|
||||||
|
presentUpgradeNag(appStoreRecord: appStoreRecord)
|
||||||
|
default:
|
||||||
|
Logger.debug("\(logTag) in \(#function) 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 messageFormat = NSLocalizedString("APP_UPDATE_NAG_ALERT_MESSAGE_FORMAT", comment: "Message format for the 'new app version available' alert. Embeds: {{The latest app version number}}")
|
||||||
|
let message = String(format: messageFormat, appStoreRecord.version)
|
||||||
|
let buttonTitle = NSLocalizedString("APP_UPDATE_NAG_ALERT_UPDATE_BUTTON", comment: "Label for the 'update' button in the 'new app version available' alert.")
|
||||||
|
|
||||||
|
OWSAlerts.showAlert(title: title,
|
||||||
|
message: message,
|
||||||
|
buttonTitle: buttonTitle,
|
||||||
|
buttonAction: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.showAppStore(appStoreURL: appStoreRecord.appStoreURL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAppStore(appStoreURL: URL) {
|
||||||
|
Logger.debug("\(logTag) in \(#function)")
|
||||||
|
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<AppStoreRecord> {
|
||||||
|
Logger.debug("\(logTag) in \(#function) lookupURL:\(lookupURL)")
|
||||||
|
|
||||||
|
let (promise, fulfill, reject) = Promise<AppStoreRecord>.pending()
|
||||||
|
|
||||||
|
let task = URLSession.ephemeral.dataTask(with: lookupURL) { (data, _, error) in
|
||||||
|
guard let data = data else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) data was unexpectedly nil")
|
||||||
|
reject(OWSErrorMakeUnableToProcessServerResponseError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let resultSet = try decoder.decode(AppStoreLookupResultSet.self, from: data)
|
||||||
|
guard let appStoreRecord = resultSet.results.first else {
|
||||||
|
owsFail("\(self.logTag) in \(#function) record was unexpectedly nil")
|
||||||
|
reject(OWSErrorMakeUnableToProcessServerResponseError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fulfill(appStoreRecord)
|
||||||
|
} catch {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URLSession {
|
||||||
|
static var ephemeral: URLSession {
|
||||||
|
return URLSession(configuration: .ephemeral)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue