diff --git a/Share/ShareRootController.swift b/Share/ShareRootController.swift index 3532d4fcb7..e397e65caf 100644 --- a/Share/ShareRootController.swift +++ b/Share/ShareRootController.swift @@ -13,7 +13,7 @@ private final class SharedExtensionContext { init(sharedContext: SharedAccountContext) { self.sharedContext = sharedContext - self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in + self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in return sharedContext.accountUserInterfaceInUse(id) }) } diff --git a/Telegram-iOS/AppDelegate.swift b/Telegram-iOS/AppDelegate.swift index dd5b50e1d7..c01c03bbcc 100644 --- a/Telegram-iOS/AppDelegate.swift +++ b/Telegram-iOS/AppDelegate.swift @@ -661,7 +661,25 @@ private final class SharedApplicationContext { return .single(nil) } } - let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: liveLocationPolling, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in + let watchTasks = self.context.get() + |> mapToSignal { context -> Signal in + if let context = context, let watchManager = context.context.watchManager { + let accountId = context.context.account.id + return watchManager.runningTasks + |> distinctUntilChanged + |> map { value -> AccountRecordId? in + if let value = value, value.running { + return accountId + } else { + return nil + } + } + |> distinctUntilChanged + } else { + return .single(nil) + } + } + let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in return sharedContext.accountUserInterfaceInUse(id) }) let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager) @@ -891,6 +909,29 @@ private final class SharedApplicationContext { } }) } + self.mainWindow.forEachViewController({ controller in + if let controller = controller as? TabBarAccountSwitchController { + var dismissed = false + if let rootController = self.mainWindow.viewController as? TelegramRootController { + if let tabsController = rootController.viewControllers.first as? TabBarController { + for i in 0 ..< tabsController.controllers.count { + if let _ = tabsController.controllers[i] as? (SettingsController & ViewController) { + let sourceNodes = tabsController.sourceNodesForController(at: i) + if let sourceNodes = sourceNodes { + dismissed = true + controller.dismiss(sourceNodes: sourceNodes) + } + return false + } + } + } + } + if dismissed { + controller.dismiss() + } + } + return true + }) self.mainWindow.topLevelOverlayControllers = [sharedApplicationContext.overlayMediaController, context.notificationController] var authorizeNotifications = true if #available(iOS 10.0, *) { @@ -937,7 +978,9 @@ private final class SharedApplicationContext { } })) - self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context)) + self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context, allowBackgroundTimeExtension: { timeout in + wakeupManager.allowBackgroundTimeExtension(timeout: timeout) + })) let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in if let manager = manager { watchManagerArgumentsPromise.set(.single(manager.arguments)) @@ -1578,16 +1621,33 @@ private final class SharedApplicationContext { self.openChatWhenReady(accountId: accountIdFromNotification(response.notification), peerId: peerId, messageId: messageId) } completionHandler() - } else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification) { - if let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty { - let text = response.userText - let signal = self.authorizedContext() - |> take(1) - |> mapToSignal { context -> Signal in - if let messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) { - let _ = applyMaxReadIndexInteractively(postbox: context.context.account.postbox, stateManager: context.context.account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)).start() + } else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification), let accountId = accountIdFromNotification(response.notification) { + guard let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty else { + completionHandler() + return + } + let text = response.userText + let signal = self.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { sharedContext -> Signal in + sharedContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0) + return sharedContext.sharedContext.activeAccounts + |> mapToSignal { _, accounts, _ -> Signal in + for account in accounts { + if account.1.id == accountId { + return .single(account.1) + } } - return enqueueMessages(account: context.context.account, peerId: peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]) + return .complete() + } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { account -> Signal in + if let messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) { + let _ = applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)).start() + } + return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]) |> map { messageIds -> MessageId? in if messageIds.isEmpty { return nil @@ -1597,7 +1657,7 @@ private final class SharedApplicationContext { } |> mapToSignal { messageId -> Signal in if let messageId = messageId { - return context.context.account.postbox.unsentMessageIdsView() + return account.postbox.unsentMessageIdsView() |> filter { view in return !view.ids.contains(messageId) } @@ -1610,34 +1670,20 @@ private final class SharedApplicationContext { } } } - |> deliverOnMainQueue - |> timeout(15.0, queue: Queue.mainQueue(), alternate: .complete() |> beforeCompleted { - /*let content = UNMutableNotificationContent() - content.body = "Please open the app to continue sending messages" - content.sound = UNNotificationSound.default() - content.categoryIdentifier = "error" - content.userInfo = ["peerId": peerId as NSNumber] - - let request = UNNotificationRequest(identifier: "reply-error", content: content, trigger: nil) - - let center = UNUserNotificationCenter.current() - center.add(request)*/ - }) - - let disposable = MetaDisposable() - disposable.set((signal - |> afterDisposed { [weak disposable] in - Queue.mainQueue().async { - if let disposable = disposable { - self.replyFromNotificationsDisposables.remove(disposable) - } - completionHandler() - } - }).start()) - self.replyFromNotificationsDisposables.add(disposable) - } else { - completionHandler() } + |> deliverOnMainQueue + + let disposable = MetaDisposable() + disposable.set((signal + |> afterDisposed { [weak disposable] in + Queue.mainQueue().async { + if let disposable = disposable { + self.replyFromNotificationsDisposables.remove(disposable) + } + completionHandler() + } + }).start()) + self.replyFromNotificationsDisposables.add(disposable) } else { completionHandler() } diff --git a/Telegram-iOS/ApplicationContext.swift b/Telegram-iOS/ApplicationContext.swift index 6bd6ad494e..759c500bde 100644 --- a/Telegram-iOS/ApplicationContext.swift +++ b/Telegram-iOS/ApplicationContext.swift @@ -389,7 +389,11 @@ final class AuthorizedApplicationContext { }*/ if let tabsController = strongSelf.rootController.viewControllers.first as? TabBarController, !tabsController.controllers.isEmpty, tabsController.selectedIndex >= 0 { let controller = tabsController.controllers[tabsController.selectedIndex] - strongSelf.isReady.set(controller.ready.get()) + let combinedReady = combineLatest(tabsController.ready.get(), controller.ready.get()) + |> map { $0 && $1 } + |> filter { $0 } + |> take(1) + strongSelf.isReady.set(combinedReady) } else { strongSelf.isReady.set(.single(true)) } @@ -762,7 +766,8 @@ final class AuthorizedApplicationContext { } }) - let _ = (watchManagerArguments |> deliverOnMainQueue).start(next: { [weak self] arguments in + let _ = (watchManagerArguments + |> deliverOnMainQueue).start(next: { [weak self] arguments in guard let strongSelf = self else { return } diff --git a/Telegram-iOS/SharedWakeupManager.swift b/Telegram-iOS/SharedWakeupManager.swift index 7f5ab9353e..5e144be64d 100644 --- a/Telegram-iOS/SharedWakeupManager.swift +++ b/Telegram-iOS/SharedWakeupManager.swift @@ -12,6 +12,7 @@ private struct AccountTasks { let backgroundDownloads: Bool let backgroundAudio: Bool let activeCalls: Bool + let watchTasks: Bool let userInterfaceInUse: Bool var isEmpty: Bool { @@ -33,6 +34,9 @@ private struct AccountTasks { if self.activeCalls { return false } + if self.watchTasks { + return false + } if self.userInterfaceInUse { return false } @@ -57,7 +61,7 @@ final class SharedWakeupManager { private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] - init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal) { + init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal, watchTasks: Signal, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal) { assert(Queue.mainQueue().isCurrent()) self.beginBackgroundTask = beginBackgroundTask @@ -120,11 +124,17 @@ final class SharedWakeupManager { } |> distinctUntilChanged + let hasWatchTasks = watchTasks + |> map { id in + return id == account.id + } + |> distinctUntilChanged + let userInterfaceInUse = accountUserInterfaceInUse(account.id) - return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, userInterfaceInUse) - |> map { importantTasksRunning, isPollingState, hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, userInterfaceInUse -> (Account, Bool, AccountTasks) in - return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: hasActiveCalls, userInterfaceInUse: userInterfaceInUse)) + return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager?.isPollingState(accountId: account.id) ?? .single(false), hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse) + |> map { importantTasksRunning, isPollingState, hasActiveAudio, hasActiveCalls, hasActiveLiveLocationPolling, hasWatchTasks, userInterfaceInUse -> (Account, Bool, AccountTasks) in + return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: hasActiveLiveLocationPolling, backgroundDownloads: false, backgroundAudio: hasActiveAudio, activeCalls: hasActiveCalls, watchTasks: hasWatchTasks, userInterfaceInUse: userInterfaceInUse)) } } return combineLatest(signals) diff --git a/Telegram-iOS/TGBridgeServer.h b/Telegram-iOS/TGBridgeServer.h index dc353f301e..6abccb783c 100644 --- a/Telegram-iOS/TGBridgeServer.h +++ b/Telegram-iOS/TGBridgeServer.h @@ -9,7 +9,7 @@ @property (nonatomic, readonly) bool isRunning; -- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction; +- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction allowBackgroundTimeExtension:(void (^)())allowBackgroundTimeExtension; - (void)startRunning; - (SSignal *)watchAppInstalledSignal; diff --git a/Telegram-iOS/TGBridgeServer.m b/Telegram-iOS/TGBridgeServer.m index 1b0ce07fd3..6ceb879f3d 100644 --- a/Telegram-iOS/TGBridgeServer.m +++ b/Telegram-iOS/TGBridgeServer.m @@ -49,6 +49,8 @@ NSMutableDictionary *_runningTasks; SVariable *_hasRunningTasks; + + void (^_allowBackgroundTimeExtension)(); } @property (nonatomic, readonly) WCSession *session; @@ -57,7 +59,7 @@ @implementation TGBridgeServer -- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction +- (instancetype)initWithHandler:(SSignal *(^)(TGBridgeSubscription *))handler fileHandler:(void (^)(NSString *, NSDictionary *))fileHandler dispatchOnQueue:(void (^)(void (^)(void)))dispatchOnQueue logFunction:(void (^)(NSString *))logFunction allowBackgroundTimeExtension:(void (^)())allowBackgroundTimeExtension { self = [super init]; if (self != nil) @@ -66,6 +68,7 @@ _fileHandler = [fileHandler copy]; _dispatch = [dispatchOnQueue copy]; _logFunction = [logFunction copy]; + _allowBackgroundTimeExtension = [allowBackgroundTimeExtension copy]; _runningTasks = [[NSMutableDictionary alloc] init]; _hasRunningTasks = [[SVariable alloc] init]; @@ -162,6 +165,10 @@ - (void)handleMessageData:(NSData *)messageData task:(id)task replyHandler:(void (^)(NSData *))replyHandler completion:(void (^)(void))completion { + if (_allowBackgroundTimeExtension) { + _allowBackgroundTimeExtension(); + } + __block id runningTask = task; void (^finishTask)(NSTimeInterval) = ^(NSTimeInterval delay) { diff --git a/Telegram-iOS/WatchCommunicationManager.swift b/Telegram-iOS/WatchCommunicationManager.swift index 5d4c42fe67..a35d14ad8c 100644 --- a/Telegram-iOS/WatchCommunicationManager.swift +++ b/Telegram-iOS/WatchCommunicationManager.swift @@ -6,6 +6,8 @@ import TelegramUI final class WatchCommunicationManager { private let queue: Queue + private let allowBackgroundTimeExtension: (Double) -> Void + private var server: TGBridgeServer! private let contextDisposable = MetaDisposable() @@ -15,8 +17,9 @@ final class WatchCommunicationManager { private let presets = Promise(nil) private let navigateToMessagePipe = ValuePipe() - init(queue: Queue, context: Promise) { + init(queue: Queue, context: Promise, allowBackgroundTimeExtension: @escaping (Double) -> Void) { self.queue = queue + self.allowBackgroundTimeExtension = allowBackgroundTimeExtension let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in var map = map @@ -50,6 +53,8 @@ final class WatchCommunicationManager { if let value = value { Logger.shared.log("WatchBridge", value) } + }, allowBackgroundTimeExtension: { + allowBackgroundTimeExtension(4.0) }) self.server.startRunning() @@ -173,12 +178,12 @@ final class WatchCommunicationManager { } } -func watchCommunicationManager(context: Promise) -> Signal { +func watchCommunicationManager(context: Promise, allowBackgroundTimeExtension: @escaping (Double) -> Void) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { if #available(iOSApplicationExtension 9.0, *) { - subscriber.putNext(WatchCommunicationManager(queue: queue, context: context)) + subscriber.putNext(WatchCommunicationManager(queue: queue, context: context, allowBackgroundTimeExtension: allowBackgroundTimeExtension)) } else { subscriber.putNext(nil) } diff --git a/submodules/Display b/submodules/Display index 6e0355b632..26b55adfb3 160000 --- a/submodules/Display +++ b/submodules/Display @@ -1 +1 @@ -Subproject commit 6e0355b6326c684f465c691dbaef6d147bda38f3 +Subproject commit 26b55adfb35df215fe6ec3e6a9f7c83748ec7cd5 diff --git a/submodules/TelegramUI b/submodules/TelegramUI index 8ddc0b670a..06625eea98 160000 --- a/submodules/TelegramUI +++ b/submodules/TelegramUI @@ -1 +1 @@ -Subproject commit 8ddc0b670a139a3c37be807f7ab2e612351ff813 +Subproject commit 06625eea98a27e8c41a4ef74ddeb1055b5ce036d