diff --git a/Config/app_configuration.bzl b/Config/app_configuration.bzl index dbc6f547a5..da3e824e1e 100644 --- a/Config/app_configuration.bzl +++ b/Config/app_configuration.bzl @@ -2,7 +2,7 @@ def appConfig(): apiId = native.read_config("custom", "apiId") apiHash = native.read_config("custom", "apiHash") - hockeyAppId = native.read_config("custom", "hockeyAppId") + appCenterId = native.read_config("custom", "appCenterId") isInternalBuild = native.read_config("custom", "isInternalBuild") isAppStoreBuild = native.read_config("custom", "isAppStoreBuild") appStoreId = native.read_config("custom", "appStoreId") @@ -11,7 +11,7 @@ def appConfig(): return { "apiId": apiId, "apiHash": apiHash, - "hockeyAppId": hockeyAppId, + "appCenterId": appCenterId, "isInternalBuild": isInternalBuild, "isAppStoreBuild": isAppStoreBuild, "appStoreId": appStoreId, diff --git a/Makefile b/Makefile index ff68b48e5b..fccbd74086 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUCK_OPTIONS=\ --config custom.baseApplicationBundleId="${BUNDLE_ID}" \ --config custom.apiId="${API_ID}" \ --config custom.apiHash="${API_HASH}" \ - --config custom.hockeyAppId="${HOCKEYAPP_ID}" \ + --config custom.appCenterId="${APP_CENTER_ID}" \ --config custom.isInternalBuild="${IS_INTERNAL_BUILD}" \ --config custom.isAppStoreBuild="${IS_APPSTORE_BUILD}" \ --config custom.appStoreId="${APPSTORE_ID}" \ diff --git a/submodules/BuildConfig/BUCK b/submodules/BuildConfig/BUCK index 6298c2a05c..91dd26cf19 100644 --- a/submodules/BuildConfig/BUCK +++ b/submodules/BuildConfig/BUCK @@ -9,7 +9,7 @@ static_library( compiler_flags = [ '-DAPP_CONFIG_API_ID=' + appConfig()["apiId"], '-DAPP_CONFIG_API_HASH="' + appConfig()["apiHash"] + '"', - '-DAPP_CONFIG_HOCKEYAPP_ID="' + appConfig()["hockeyAppId"] + '"', + '-DAPP_CONFIG_APP_CENTER_ID="' + appConfig()["appCenterId"] + '"', '-DAPP_CONFIG_IS_INTERNAL_BUILD=' + appConfig()["isInternalBuild"], '-DAPP_CONFIG_IS_APPSTORE_BUILD=' + appConfig()["isAppStoreBuild"], '-DAPP_CONFIG_APPSTORE_ID=' + appConfig()["appStoreId"], diff --git a/submodules/BuildConfig/Sources/BuildConfig.h b/submodules/BuildConfig/Sources/BuildConfig.h index d13c28dc3f..562a63ce27 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.h +++ b/submodules/BuildConfig/Sources/BuildConfig.h @@ -11,7 +11,7 @@ - (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId; -@property (nonatomic, strong, readonly) NSString * _Nullable hockeyAppId; +@property (nonatomic, strong, readonly) NSString * _Nullable appCenterId; @property (nonatomic, readonly) int32_t apiId; @property (nonatomic, strong, readonly) NSString * _Nonnull apiHash; @property (nonatomic, readonly) bool isInternalBuild; diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index 6c17f8f9eb..f4808b26cb 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -72,7 +72,7 @@ API_AVAILABLE(ios(10)) NSData * _Nullable _bundleData; int32_t _apiId; NSString * _Nonnull _apiHash; - NSString * _Nullable _hockeyAppId; + NSString * _Nullable _appCenterId; NSMutableDictionary * _Nonnull _dataDict; } @@ -129,7 +129,7 @@ API_AVAILABLE(ios(10)) if (self != nil) { _apiId = APP_CONFIG_API_ID; _apiHash = @(APP_CONFIG_API_HASH); - _hockeyAppId = @(APP_CONFIG_HOCKEYAPP_ID); + _appCenterId = @(APP_CONFIG_APP_CENTER_ID); _dataDict = [[NSMutableDictionary alloc] init]; @@ -163,8 +163,8 @@ API_AVAILABLE(ios(10)) return _apiHash; } -- (NSString * _Nullable)hockeyAppId { - return _hockeyAppId; +- (NSString * _Nullable)appCenterId { + return _appCenterId; } - (bool)isInternalBuild { diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 02bcf8dd75..3750a848fd 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -5,7 +5,6 @@ import TelegramCore import SyncCore import UserNotifications import Intents -//import HockeySDK import Postbox import PushKit import AsyncDisplayKit @@ -157,12 +156,13 @@ final class SharedApplicationContext { } } -@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate/*, BITHockeyManagerDelegate*/, UNUserNotificationCenterDelegate, UIAlertViewDelegate { +@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate, UIAlertViewDelegate { @objc var window: UIWindow? var nativeWindow: (UIWindow & WindowHost)? var mainWindow: Window1! private var dataImportSplash: LegacyDataImportSplash? + private var buildConfig: BuildConfig? let episodeId = arc4random() private let isInForegroundPromise = ValuePromise(false, ignoreRepeated: true) @@ -369,6 +369,7 @@ final class SharedApplicationContext { let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId) + self.buildConfig = buildConfig let signatureDict = BuildConfigExtra.signatureDict() let apiId: Int32 = buildConfig.apiId @@ -1338,46 +1339,6 @@ final class SharedApplicationContext { self.isActivePromise.set(true) } - /*BITHockeyBaseManager.setPresentAlert({ [weak self] alert in - if let strongSelf = self, let alert = alert { - var actions: [TextAlertAction] = [] - for action in alert.actions { - let isDefault = action.style == .default - actions.append(TextAlertAction(type: isDefault ? .defaultAction : .genericAction, title: action.title ?? "", action: { - if let action = action as? BITAlertAction { - action.invokeAction() - } - })) - } - if let sharedContext = strongSelf.contextValue?.context.sharedContext { - let presentationData = sharedContext.currentPresentationData.with { $0 } - strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: alert.title, text: alert.message ?? "", actions: actions), on: .root) - } - } - }) - - BITHockeyBaseManager.setPresentView({ [weak self] controller in - if let strongSelf = self, let controller = controller { - let parent = LegacyController(presentation: .modal(animateIn: true), theme: nil) - let navigationController = UINavigationController(rootViewController: controller) - controller.navigation_setDismiss({ [weak parent] in - parent?.dismiss() - }, rootController: nil) - parent.bind(controller: navigationController) - strongSelf.mainWindow.present(parent, on: .root) - } - }) - - if let hockeyAppId = buildConfig.hockeyAppId, !hockeyAppId.isEmpty { - BITHockeyManager.shared().configure(withIdentifier: hockeyAppId, delegate: self) - BITHockeyManager.shared().crashManager.crashManagerStatus = .alwaysAsk - BITHockeyManager.shared().start() - #if targetEnvironment(simulator) - #else - BITHockeyManager.shared().authenticator.authenticateInstallation() - #endif - }*/ - if UIApplication.shared.isStatusBarHidden { UIApplication.shared.setStatusBarHidden(false, with: .none) } @@ -1403,6 +1364,8 @@ final class SharedApplicationContext { }) }*/ + self.maybeCheckForUpdates() + return true } @@ -1486,6 +1449,8 @@ final class SharedApplicationContext { self.isInForegroundPromise.set(true) self.isActiveValue = true self.isActivePromise.set(true) + + self.maybeCheckForUpdates() } func applicationWillTerminate(_ application: UIApplication) { @@ -2204,6 +2169,60 @@ final class SharedApplicationContext { completionHandler() } + private var lastCheckForUpdatesTimestamp: Double? + private let currentCheckForUpdatesDisposable = MetaDisposable() + + private func maybeCheckForUpdates() { + #if targetEnvironment(simulator) + return; + #endif + + guard let buildConfig = self.buildConfig, !buildConfig.isAppStoreBuild, let appCenterId = buildConfig.appCenterId, !appCenterId.isEmpty else { + return + } + let timestamp = CFAbsoluteTimeGetCurrent() + if self.lastCheckForUpdatesTimestamp == nil || self.lastCheckForUpdatesTimestamp! < timestamp - 10.0 * 60.0 { + self.lastCheckForUpdatesTimestamp = timestamp + + if let url = URL(string: "https://api.appcenter.ms/v0.1/public/sdk/apps/\(appCenterId)/releases/latest") { + self.currentCheckForUpdatesDisposable.set((downloadHTTPData(url: url) + |> deliverOnMainQueue).start(next: { [weak self] data in + guard let strongSelf = self else { + return + } + guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { + return + } + guard let dict = json as? [String: Any] else { + return + } + guard let versionString = dict["version"] as? String, let version = Int(versionString) else { + return + } + guard let releaseNotesUrl = dict["release_notes_url"] as? String else { + return + } + guard let currentVersionString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String, let currentVersion = Int(currentVersionString) else { + return + } + if currentVersion < version { + let _ = (strongSelf.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { sharedContext in + let presentationData = sharedContext.sharedContext.currentPresentationData.with { $0 } + sharedContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "A new build is available", actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: "Show", action: { + sharedContext.sharedContext.applicationBindings.openUrl(releaseNotesUrl) + }) + ]), on: .root, blockInteraction: false, completion: {}) + }) + } + })) + } + } + } + override var next: UIResponder? { if let context = self.contextValue, let controller = context.context.keyShortcutsController { return controller @@ -2345,3 +2364,29 @@ private func messageIdFromNotification(peerId: PeerId, notification: UNNotificat } return nil } + +private enum DownloadFileError { + case network +} + +private func downloadHTTPData(url: URL) -> Signal { + return Signal { subscriber in + let completed = Atomic(value: false) + let downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { location, _, error in + let _ = completed.swap(true) + if let location = location, let data = try? Data(contentsOf: location) { + subscriber.putNext(data) + subscriber.putCompletion() + } else { + subscriber.putError(.network) + } + }) + downloadTask.resume() + + return ActionDisposable { + if !completed.with({ $0 }) { + downloadTask.cancel() + } + } + } +}