mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
881 lines
50 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|