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