Swiftgram/Telegram-iOS/ApplicationContext.swift
2019-02-15 17:32:15 +04:00

881 lines
50 KiB
Swift

import Foundation
import Intents
import TelegramUI
import SwiftSignalKit
import Postbox
import TelegramCore
import Display
import LegacyComponents
func isAccessLocked(data: PostboxAccessChallengeData, at timestamp: Int32) -> Bool {
if data.isLockable, let autolockDeadline = data.autolockDeadline, autolockDeadline <= timestamp {
return true
} else {
return false
}
}
final class UnauthorizedApplicationContext {
let sharedContext: SharedAccountContext
let account: UnauthorizedAccount
let rootController: AuthorizationSequenceController
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId)?, [(String, AccountRecordId)])) {
self.sharedContext = sharedContext
self.account = account
let presentationData = sharedContext.currentPresentationData.with { $0 }
self.rootController = AuthorizationSequenceController(sharedContext: sharedContext, account: account, otherAccountPhoneNumbers: otherAccountPhoneNumbers, strings: presentationData.strings, theme: presentationData.theme, openUrl: sharedContext.applicationBindings.openUrl, apiId: BuildConfig.shared().apiId, apiHash: BuildConfig.shared().apiHash)
account.shouldBeServiceTaskMaster.set(sharedContext.applicationBindings.applicationInForeground |> map { value -> AccountServiceTaskMasterMode in
if value {
return .always
} else {
return .never
}
})
}
}
private struct PasscodeState: Equatable {
let isActive: Bool
let challengeData: PostboxAccessChallengeData
let autolockTimeout: Int32?
let enableBiometrics: Bool
}
final class AuthorizedApplicationContext {
let mainWindow: Window1
let lockedCoveringView: LockedWindowCoveringView
let context: AccountContext
let rootController: TelegramRootController
let notificationController: NotificationContainerController
private var scheduledOperChatWithPeerId: PeerId?
private var scheduledOpenExternalUrl: URL?
private let passcodeStatusDisposable = MetaDisposable()
private let passcodeLockDisposable = MetaDisposable()
private let loggedOutDisposable = MetaDisposable()
private let inAppNotificationSettingsDisposable = MetaDisposable()
private let notificationMessagesDisposable = MetaDisposable()
private let termsOfServiceUpdatesDisposable = MetaDisposable()
private let termsOfServiceProceedToBotDisposable = MetaDisposable()
private let watchNavigateToMessageDisposable = MetaDisposable()
private let permissionsDisposable = MetaDisposable()
private var inAppNotificationSettings: InAppNotificationSettings?
private var isLocked: Bool = true
private var passcodeController: ViewController?
private var currentTermsOfServiceUpdate: TermsOfServiceUpdate?
private var currentPermissionsController: PermissionController?
private let unlockedStatePromise = Promise<Bool>()
var unlockedState: Signal<Bool, NoError> {
return self.unlockedStatePromise.get()
}
var applicationBadge: Signal<Int32, NoError> {
return renderedTotalUnreadCount(accountManager: self.context.sharedContext.accountManager, postbox: self.context.account.postbox)
|> map {
$0.0
}
}
let isReady = Promise<Bool>()
private var presentationDataDisposable: Disposable?
private var displayAlertsDisposable: Disposable?
private var removeNotificationsDisposable: Disposable?
private var applicationInForegroundDisposable: Disposable?
private var showCallsTab: Bool
private var showCallsTabDisposable: Disposable?
private var enablePostboxTransactionsDiposable: Disposable?
init(mainWindow: Window1, watchManagerArguments: Signal<WatchManagerArguments?, NoError>, context: AccountContext, accountManager: AccountManager, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) {
setupLegacyComponents(context: context)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.mainWindow = mainWindow
self.lockedCoveringView = LockedWindowCoveringView(theme: presentationData.theme)
self.context = context
let runningWatchTasksPromise = Promise<WatchRunningTasks?>(nil)
let runningDownloadTasks = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]), context.account.shouldKeepBackgroundDownloadConnections.get())
|> map { sharedData, shouldKeepBackgroundDownloadConnections -> Bool in
let settings: AutomaticMediaDownloadSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings ?? AutomaticMediaDownloadSettings.defaultSettings
if !settings.downloadInBackground {
return false
}
return shouldKeepBackgroundDownloadConnections
}
|> distinctUntilChanged
self.showCallsTab = showCallsTab
self.notificationController = NotificationContainerController(context: context)
self.mainWindow.previewThemeAccentColor = presentationData.theme.rootController.navigationBar.accentTextColor
self.mainWindow.previewThemeDarkBlur = presentationData.theme.chatList.searchBarKeyboardColor == .dark
self.mainWindow.setupVolumeControlStatusBarGraphics(presentationData.volumeControlStatusBarIcons.images)
self.rootController = TelegramRootController(context: context)
if KeyShortcutsController.isAvailable {
let keyShortcutsController = KeyShortcutsController { [weak self] f in
if let strongSelf = self {
if let tabController = strongSelf.rootController.rootTabController {
let controller = tabController.controllers[tabController.selectedIndex]
if !f(controller) {
return
}
if let controller = strongSelf.rootController.topViewController as? ViewController {
if !f(controller) {
return
}
}
}
strongSelf.mainWindow.forEachViewController(f)
}
}
context.keyShortcutsController = keyShortcutsController
}
/*self.applicationInForegroundDisposable = context.sharedContext.applicationBindings.applicationInForeground.start(next: { [weak self] value in
Queue.mainQueue().async {
self?.notificationManager.isApplicationInForeground = value
}
})*/
let previousPasscodeState = Atomic<PasscodeState?>(value: nil)
self.passcodeStatusDisposable.set((combineLatest(queue: Queue.mainQueue(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), context.sharedContext.accountManager.accessChallengeData(), context.sharedContext.applicationBindings.applicationIsActive)
|> map { sharedData, accessChallengeDataView, isActive -> (PostboxAccessChallengeData, PresentationPasscodeSettings?, Bool) in
let accessChallengeData = accessChallengeDataView.data
let passcodeSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationPasscodeSettings] as? PresentationPasscodeSettings
return (accessChallengeData, passcodeSettings, isActive)
}
|> map { accessChallengeData, passcodeSettings, isActive -> PasscodeState in
return PasscodeState(isActive: isActive, challengeData: accessChallengeData, autolockTimeout: passcodeSettings?.autolockTimeout, enableBiometrics: passcodeSettings?.enableBiometrics ?? false)
}).start(next: { [weak self] updatedState in
guard let strongSelf = self else {
return
}
let previousState = previousPasscodeState.swap(updatedState)
var updatedAutolockDeadline: Int32?
if updatedState.isActive != previousState?.isActive, let autolockTimeout = updatedState.autolockTimeout {
updatedAutolockDeadline = Int32(CFAbsoluteTimeGetCurrent()) + max(10, autolockTimeout)
}
var effectiveAutolockDeadline = updatedState.challengeData.autolockDeadline
if updatedState.isActive {
} else if previousState != nil && previousState!.autolockTimeout != updatedState.autolockTimeout {
effectiveAutolockDeadline = updatedAutolockDeadline
}
if let previousState = previousState, previousState.isActive, !updatedState.isActive, effectiveAutolockDeadline != 0 {
effectiveAutolockDeadline = updatedAutolockDeadline
}
var isLocked = false
if isAccessLocked(data: updatedState.challengeData.withUpdatedAutolockDeadline(effectiveAutolockDeadline), at: Int32(CFAbsoluteTimeGetCurrent())) {
isLocked = true
updatedAutolockDeadline = 0
}
let isLockable: Bool
switch updatedState.challengeData {
case .none:
isLockable = false
default:
isLockable = true
}
if previousState?.isActive != updatedState.isActive || isLocked != strongSelf.isLocked {
if updatedAutolockDeadline != previousState?.challengeData.autolockDeadline {
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
let data = transaction.getAccessChallengeData().withUpdatedAutolockDeadline(updatedAutolockDeadline)
transaction.setAccessChallengeData(data)
}).start()
}
strongSelf.isLocked = isLocked
//strongSelf.notificationManager.isApplicationLocked = isLocked
if isLocked {
if updatedState.isActive {
if strongSelf.passcodeController == nil {
var attemptData: TGPasscodeEntryAttemptData?
if let attempts = updatedState.challengeData.attempts {
attemptData = TGPasscodeEntryAttemptData(numberOfInvalidAttempts: Int(attempts.count), dateOfLastInvalidAttempt: Double(attempts.timestamp))
}
var mode: TGPasscodeEntryControllerMode
switch updatedState.challengeData {
case .none:
mode = TGPasscodeEntryControllerModeVerifySimple
case .numericalPassword:
mode = TGPasscodeEntryControllerModeVerifySimple
case .plaintextPassword:
mode = TGPasscodeEntryControllerModeVerifyComplex
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let presentAnimated = previousState != nil && previousState!.isActive
let legacyController = LegacyController(presentation: LegacyControllerPresentation.modal(animateIn: presentAnimated), theme: presentationData.theme)
let controller = TGPasscodeEntryController(context: legacyController.context, style: TGPasscodeEntryControllerStyleDefault, mode: mode, cancelEnabled: false, allowTouchId: updatedState.enableBiometrics, attemptData: attemptData, completion: { value in
guard let strongSelf = self else {
return
}
if value != nil {
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
let data = transaction.getAccessChallengeData().withUpdatedAutolockDeadline(nil)
transaction.setAccessChallengeData(data)
}).start()
}
})!
controller.checkCurrentPasscode = { value in
if let value = value {
switch updatedState.challengeData {
case .none:
return true
case let .numericalPassword(code, _, _):
return value == code
case let .plaintextPassword(code, _, _):
return value == code
}
} else {
return false
}
}
controller.updateAttemptData = { attemptData in
guard let strongSelf = self else {
return
}
let _ = strongSelf.context.sharedContext.accountManager.transaction({ transaction -> Void in
var attempts: AccessChallengeAttempts?
if let attemptData = attemptData {
attempts = AccessChallengeAttempts(count: Int32(attemptData.numberOfInvalidAttempts), timestamp: Int32(attemptData.dateOfLastInvalidAttempt))
}
var data = transaction.getAccessChallengeData()
switch data {
case .none:
break
case let .numericalPassword(value, timeout, _):
data = .numericalPassword(value: value, timeout: timeout, attempts: attempts)
case let .plaintextPassword(value, timeout, _):
data = .plaintextPassword(value: value, timeout: timeout, attempts: attempts)
}
transaction.setAccessChallengeData(data)
}).start()
}
controller.touchIdCompletion = {
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
let data = transaction.getAccessChallengeData().withUpdatedAutolockDeadline(nil)
transaction.setAccessChallengeData(data)
}).start()
}
legacyController.bind(controller: controller)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
legacyController.statusBar.statusBarStyle = .White
strongSelf.passcodeController = legacyController
strongSelf.unlockedStatePromise.set(.single(false))
if presentAnimated {
legacyController.presentationCompleted = {
if let strongSelf = self {
strongSelf.rootController.view.isHidden = true
strongSelf.context.sharedContext.mediaManager.overlayMediaManager.controller?.view.isHidden = true
strongSelf.notificationController.view.isHidden = true
}
}
} else {
strongSelf.rootController.view.isHidden = true
strongSelf.context.sharedContext.mediaManager.overlayMediaManager.controller?.view.isHidden = true
strongSelf.notificationController.view.isHidden = true
}
strongSelf.mainWindow.present(legacyController, on: .passcode)
if !presentAnimated {
controller.refreshTouchId()
}
} else if previousState?.isActive != updatedState.isActive, updatedState.isActive, let passcodeController = strongSelf.passcodeController as? LegacyController {
if let controller = passcodeController.legacyController as? TGPasscodeEntryController {
controller.refreshTouchId()
}
}
strongSelf.updateCoveringViewSnaphot(false)
strongSelf.mainWindow.coveringView = nil
} else {
strongSelf.unlockedStatePromise.set(.single(false))
strongSelf.updateCoveringViewSnaphot(true)
strongSelf.mainWindow.coveringView = strongSelf.lockedCoveringView
strongSelf.rootController.view.isHidden = true
strongSelf.context.sharedContext.mediaManager.overlayMediaManager.controller?.view.isHidden = true
strongSelf.notificationController.view.isHidden = true
}
} else {
if !updatedState.isActive && updatedState.autolockTimeout != nil && isLockable {
strongSelf.updateCoveringViewSnaphot(true)
strongSelf.mainWindow.coveringView = strongSelf.lockedCoveringView
strongSelf.rootController.view.isHidden = true
strongSelf.context.sharedContext.mediaManager.overlayMediaManager.controller?.view.isHidden = true
strongSelf.notificationController.view.isHidden = true
} else {
strongSelf.updateCoveringViewSnaphot(false)
strongSelf.mainWindow.coveringView = nil
strongSelf.rootController.view.isHidden = false
strongSelf.context.sharedContext.mediaManager.overlayMediaManager.controller?.view.isHidden = false
strongSelf.notificationController.view.isHidden = false
if strongSelf.rootController.rootTabController == nil {
strongSelf.rootController.addRootControllers(showCallsTab: strongSelf.showCallsTab)
if let peerId = strongSelf.scheduledOperChatWithPeerId {
strongSelf.scheduledOperChatWithPeerId = nil
strongSelf.openChatWithPeerId(peerId: peerId)
}
if let url = strongSelf.scheduledOpenExternalUrl {
strongSelf.scheduledOpenExternalUrl = nil
strongSelf.openUrl(url)
}
if #available(iOS 10.0, *) {
} else {
DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { c, a in
}, openSettings: {}, { _ in })
}
if let passcodeController = strongSelf.passcodeController {
if let chatListController = strongSelf.rootController.chatListController {
let _ = chatListController.ready.get().start(next: { [weak passcodeController] _ in
if let strongSelf = self, let passcodeController = passcodeController, strongSelf.passcodeController === passcodeController {
strongSelf.passcodeController = nil
strongSelf.rootController.chatListController?.displayNode.recursivelyEnsureDisplaySynchronously(true)
passcodeController.dismiss()
}
})
} else {
strongSelf.passcodeController = nil
strongSelf.rootController.chatListController?.displayNode.recursivelyEnsureDisplaySynchronously(true)
passcodeController.dismiss()
}
}
} else {
if let passcodeController = strongSelf.passcodeController {
strongSelf.passcodeController = nil
passcodeController.dismiss()
}
}
}
strongSelf.unlockedStatePromise.set(.single(true))
}
}/* else if updatedAutolockDeadline != previousState?.challengeData.autolockDeadline {
let _ = (account.postbox.transaction { transaction -> Void in
let data = transaction.getAccessChallengeData().withUpdatedAutolockDeadline(updatedAutolockDeadline)
transaction.setAccessChallengeData(data)
}).start()
}*/
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())
} else {
strongSelf.isReady.set(.single(true))
}
}))
let accountId = context.account.id
self.loggedOutDisposable.set(context.account.loggedOut.start(next: { value in
if value {
Logger.shared.log("ApplicationContext", "account logged out")
let _ = logoutFromAccount(id: accountId, accountManager: accountManager).start()
}
}))
self.inAppNotificationSettingsDisposable.set(((context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])) |> deliverOnMainQueue).start(next: { [weak self] sharedData in
if let strongSelf = self {
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings {
let previousSettings = strongSelf.inAppNotificationSettings
strongSelf.inAppNotificationSettings = settings
if let previousSettings = previousSettings, previousSettings.displayNameOnLockscreen != settings.displayNameOnLockscreen {
reinitializedNotificationSettings()
}
}
}
}))
self.notificationMessagesDisposable.set((context.account.stateManager.notificationMessages
|> deliverOn(Queue.mainQueue())).start(next: { [weak self] messageList in
if let strongSelf = self, let (messages, groupId, notify) = messageList.last, let firstMessage = messages.first {
if UIApplication.shared.applicationState == .active {
var chatIsVisible = false
if let topController = strongSelf.rootController.topViewController as? ChatController, topController.traceVisibility() {
if case .peer(firstMessage.id.peerId) = topController.chatLocation {
chatIsVisible = true
} else if case let .group(topGroupId) = topController.chatLocation, topGroupId == groupId {
chatIsVisible = true
}
}
if !notify {
chatIsVisible = true
}
if !chatIsVisible {
strongSelf.mainWindow.forEachViewController({ controller in
if let controller = controller as? ChatController, case .peer(firstMessage.id.peerId) = controller.chatLocation {
chatIsVisible = true
return false
}
return true
})
}
let inAppNotificationSettings: InAppNotificationSettings
if let current = strongSelf.inAppNotificationSettings {
inAppNotificationSettings = current
} else {
inAppNotificationSettings = InAppNotificationSettings.defaultSettings
}
if !strongSelf.isLocked {
let isMuted = firstMessage.attributes.contains(where: { attribute in
if let attribute = attribute as? NotificationInfoMessageAttribute {
return attribute.flags.contains(.muted)
} else {
return false
}
})
if !isMuted {
if inAppNotificationSettings.playSounds {
serviceSoundManager.playIncomingMessageSound()
}
if inAppNotificationSettings.vibrate {
serviceSoundManager.playVibrationSound()
}
}
}
if chatIsVisible {
return
}
if inAppNotificationSettings.displayPreviews {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.notificationController.enqueue(ChatMessageNotificationItem(context: strongSelf.context, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, messages: messages, tapAction: {
if let strongSelf = self {
var foundOverlay = false
strongSelf.mainWindow.forEachViewController({ controller in
if isOverlayControllerForChatNotificationOverlayPresentation(controller) {
foundOverlay = true
return false
}
return true
})
if foundOverlay {
return true
}
if let topController = strongSelf.rootController.topViewController as? ViewController, isInlineControllerForChatNotificationOverlayPresentation(topController) {
return true
}
if let topController = strongSelf.rootController.topViewController as? ChatController, case .peer(firstMessage.id.peerId) = topController.chatLocation {
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
return false
}
for controller in strongSelf.rootController.viewControllers {
if let controller = controller as? ChatController, case .peer(firstMessage.id.peerId) = controller.chatLocation {
return true
}
}
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
navigateToChatController(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId))
}
return false
}, expandAction: { expandData in
if let strongSelf = self {
let chatController = ChatController(context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId), mode: .overlay)
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(chatController, in: .window(.root), with: ChatControllerOverlayPresentationData(expandData: expandData()))
}
}))
}
}
}
}))
self.termsOfServiceUpdatesDisposable.set((context.account.stateManager.termsOfServiceUpdate
|> deliverOnMainQueue).start(next: { [weak self] termsOfServiceUpdate in
guard let strongSelf = self, strongSelf.currentTermsOfServiceUpdate != termsOfServiceUpdate else {
return
}
strongSelf.currentTermsOfServiceUpdate = termsOfServiceUpdate
if let termsOfServiceUpdate = termsOfServiceUpdate {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var acceptImpl: ((String?) -> Void)?
var declineImpl: (() -> Void)?
let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(presentationTheme: presentationData.theme), strings: presentationData.strings, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in
acceptImpl?(proccedBot)
}, decline: {
declineImpl?()
}, openUrl: { url in
if let parsedUrl = URL(string: url) {
UIApplication.shared.openURL(parsedUrl)
}
})
acceptImpl = { [weak controller] botName in
controller?.inProgress = true
guard let strongSelf = self else {
return
}
let _ = (acceptTermsOfService(account: strongSelf.context.account, id: termsOfServiceUpdate.id)
|> deliverOnMainQueue).start(completed: {
controller?.dismiss()
if let strongSelf = self, let botName = botName {
strongSelf.termsOfServiceProceedToBotDisposable.set((resolvePeerByName(account: strongSelf.context.account, name: botName, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peerId in
if let strongSelf = self, let peerId = peerId {
self?.rootController.pushViewController(ChatController(context: strongSelf.context, chatLocation: .peer(peerId), messageId: nil))
}
}))
}
})
}
declineImpl = {
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId)
|> deliverOnMainQueue).start(next: { peer in
if let phone = (peer as? TelegramUser)?.phone {
UIApplication.shared.openURL(URL(string: "https://telegram.org/deactivate?phone=\(phone)")!)
}
})
}
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
}))
if #available(iOS 10.0, *) {
let permissionsPosition = ValuePromise(0, ignoreRepeated: true)
self.permissionsDisposable.set((combineLatest(queue: .mainQueue(), requiredPermissions(context: context), permissionUISplitTest(postbox: context.account.postbox), permissionsPosition.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.contactsPermissionWarningKey()), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.notificationsPermissionWarningKey()))
|> deliverOnMainQueue).start(next: { [weak self] required, splitTest, position, contactsPermissionWarningNotice, notificationsPermissionWarningNotice in
guard let strongSelf = self else {
return
}
let contactsTimestamp = contactsPermissionWarningNotice.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
let notificationsTimestamp = notificationsPermissionWarningNotice.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
if contactsTimestamp == nil, case .requestable = required.0.status {
ApplicationSpecificNotice.setContactsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 1)
}
if notificationsTimestamp == nil, case .requestable = required.1.status {
ApplicationSpecificNotice.setNotificationsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 1)
}
let config = splitTest.configuration
var order = config.order
if !order.contains(.siri) {
order.append(.siri)
}
var requestedPermissions: [(PermissionState, Bool)] = []
var i: Int = 0
for subject in order {
if i < position {
i += 1
continue
}
var modal = false
switch subject {
case .contacts:
if case .modal = config.contacts {
modal = true
}
if case .requestable = required.0.status, contactsTimestamp != 0 {
requestedPermissions.append((required.0, modal))
}
case .notifications:
if case .modal = config.notifications {
modal = true
}
if case .requestable = required.1.status, notificationsTimestamp != 0 {
requestedPermissions.append((required.1, modal))
}
case .siri:
if case .requestable = required.2.status {
requestedPermissions.append((required.2, false))
}
default:
break
}
i += 1
}
if let (state, modal) = requestedPermissions.first {
if modal {
var didAppear = false
let controller: PermissionController
if let currentController = strongSelf.currentPermissionsController {
controller = currentController
didAppear = true
} else {
controller = PermissionController(context: context, splitTest: splitTest)
strongSelf.currentPermissionsController = controller
}
controller.setState(state, animated: didAppear)
controller.proceed = { resolved in
permissionsPosition.set(position + 1)
switch state {
case .contacts:
ApplicationSpecificNotice.setContactsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 0)
case .notifications:
ApplicationSpecificNotice.setNotificationsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 0)
default:
break
}
}
if !didAppear {
Queue.mainQueue().after(0.15, {
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments.init(presentationAnimation: .modalSheet))
})
}
} else {
switch state {
case .contacts:
splitTest.addEvent(.ContactsRequest)
DeviceAccess.authorizeAccess(to: .contacts, context: context) { result in
if result {
splitTest.addEvent(.ContactsAllowed)
} else {
splitTest.addEvent(.ContactsDenied)
}
permissionsPosition.set(position + 1)
ApplicationSpecificNotice.setContactsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 0)
}
case .notifications:
splitTest.addEvent(.NotificationsRequest)
DeviceAccess.authorizeAccess(to: .notifications, context: context) { result in
if result {
splitTest.addEvent(.NotificationsAllowed)
} else {
splitTest.addEvent(.NotificationsDenied)
}
permissionsPosition.set(position + 1)
ApplicationSpecificNotice.setNotificationsPermissionWarning(accountManager: context.sharedContext.accountManager, value: 0)
}
case .siri:
DeviceAccess.authorizeAccess(to: .siri, context: context) { result in
permissionsPosition.set(position + 1)
}
default:
break
}
}
} else {
if let controller = strongSelf.currentPermissionsController {
strongSelf.currentPermissionsController = nil
controller.dismiss(completion: {})
}
}
}))
}
self.displayAlertsDisposable = (context.account.stateManager.displayAlerts
|> deliverOnMainQueue).start(next: { [weak self] alerts in
if let strongSelf = self{
for text in alerts {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
}
})
self.removeNotificationsDisposable = (context.account.stateManager.appliedIncomingReadMessages
|> deliverOnMainQueue).start(next: { [weak self] ids in
if let strongSelf = self {
strongSelf.context.sharedContext.applicationBindings.clearMessageNotifications(ids)
}
})
self.context.account.resetStateManagement()
let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:])
self.context.account.importableContacts.set(self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])
|> mapToSignal { sharedData -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in
let settings: ContactSynchronizationSettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings) ?? .defaultSettings
if settings.synchronizeDeviceContacts {
return importableContacts
} else {
return .single([:])
}
})
let previousTheme = Atomic<PresentationTheme?>(value: nil)
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
if previousTheme.swap(presentationData.theme) !== presentationData.theme {
strongSelf.mainWindow.previewThemeAccentColor = presentationData.theme.rootController.navigationBar.accentTextColor
strongSelf.mainWindow.previewThemeDarkBlur = presentationData.theme.chatList.searchBarKeyboardColor == .dark
strongSelf.lockedCoveringView.updateTheme(presentationData.theme)
strongSelf.rootController.updateTheme(NavigationControllerTheme(presentationTheme: presentationData.theme))
}
}
})
let showCallsTabSignal = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings])
|> map { sharedData -> Bool in
var value = true
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings] as? CallListSettings {
value = settings.showTab
}
return value
}
self.showCallsTabDisposable = (showCallsTabSignal |> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self {
if strongSelf.showCallsTab != value {
strongSelf.showCallsTab = value
strongSelf.rootController.updateRootControllers(showCallsTab: value)
}
}
})
let _ = (watchManagerArguments |> deliverOnMainQueue).start(next: { [weak self] arguments in
guard let strongSelf = self else {
return
}
let watchManager = WatchManager(arguments: arguments)
strongSelf.context.watchManager = watchManager
runningWatchTasksPromise.set(watchManager.runningTasks)
strongSelf.watchNavigateToMessageDisposable.set((strongSelf.context.sharedContext.applicationBindings.applicationInForeground |> mapToSignal({ applicationInForeground -> Signal<(Bool, MessageId), NoError> in
return watchManager.navigateToMessageRequested
|> map { messageId in
return (applicationInForeground, messageId)
}
|> deliverOnMainQueue
})).start(next: { [weak self] applicationInForeground, messageId in
if let strongSelf = self {
if applicationInForeground {
var chatIsVisible = false
if let controller = strongSelf.rootController.viewControllers.last as? ChatController, case .peer(messageId.peerId) = controller.chatLocation {
chatIsVisible = true
}
let navigateToMessage = {
navigateToChatController(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), messageId: messageId)
}
if chatIsVisible {
navigateToMessage()
} else {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.WatchRemote_AlertTitle, text: presentationData.strings.WatchRemote_AlertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.WatchRemote_AlertOpen, action:navigateToMessage)])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
} else {
//strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId)
}
}
}))
})
}
deinit {
self.context.account.postbox.clearCaches()
self.context.account.shouldKeepOnlinePresence.set(.single(false))
self.context.account.shouldBeServiceTaskMaster.set(.single(.never))
self.loggedOutDisposable.dispose()
self.inAppNotificationSettingsDisposable.dispose()
self.notificationMessagesDisposable.dispose()
self.termsOfServiceUpdatesDisposable.dispose()
self.passcodeLockDisposable.dispose()
self.passcodeStatusDisposable.dispose()
self.displayAlertsDisposable?.dispose()
self.removeNotificationsDisposable?.dispose()
self.presentationDataDisposable?.dispose()
self.enablePostboxTransactionsDiposable?.dispose()
self.termsOfServiceProceedToBotDisposable.dispose()
self.watchNavigateToMessageDisposable.dispose()
self.permissionsDisposable.dispose()
}
func openChatWithPeerId(peerId: PeerId, messageId: MessageId? = nil) {
var visiblePeerId: PeerId?
if let controller = self.rootController.topViewController as? ChatController, case let .peer(peerId) = controller.chatLocation {
visiblePeerId = peerId
}
if visiblePeerId != peerId || messageId != nil {
if self.rootController.rootTabController != nil {
navigateToChatController(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), messageId: messageId)
} else {
self.scheduledOperChatWithPeerId = peerId
}
}
}
func openUrl(_ url: URL) {
if self.rootController.rootTabController != nil {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
openExternalUrl(context: self.context, url: url.absoluteString, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in
self?.rootController.view.endEditing(true)
})
} else {
self.scheduledOpenExternalUrl = url
}
}
func openRootSearch() {
self.rootController.openChatsSearch()
}
func openRootCompose() {
self.rootController.openRootCompose()
}
func openRootCamera() {
self.rootController.openRootCamera()
}
private func updateCoveringViewSnaphot(_ visible: Bool) {
if visible {
let scale: CGFloat = 0.5
let unscaledSize = self.mainWindow.hostView.containerView.frame.size
let image = generateImage(CGSize(width: floor(unscaledSize.width * scale), height: floor(unscaledSize.height * scale)), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.scaleBy(x: scale, y: scale)
UIGraphicsPushContext(context)
self.mainWindow.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
UIGraphicsPopContext()
})?.applyScreenshotEffect()
self.lockedCoveringView.updateSnapshot(image)
} else {
self.lockedCoveringView.updateSnapshot(nil)
}
}
}