mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
908 lines
49 KiB
Swift
908 lines
49 KiB
Swift
import Foundation
|
|
import Intents
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import Display
|
|
import LegacyComponents
|
|
import DeviceAccess
|
|
import TelegramUpdateUI
|
|
import AccountContext
|
|
import AlertUI
|
|
import PresentationDataUtils
|
|
import TelegramPermissions
|
|
import TelegramNotices
|
|
import LegacyUI
|
|
import TelegramPermissionsUI
|
|
import PasscodeUI
|
|
import ImageBlur
|
|
import FastBlur
|
|
import WatchBridge
|
|
import SettingsUI
|
|
import AppLock
|
|
import AccountUtils
|
|
import ContextUI
|
|
import TelegramCallsUI
|
|
|
|
final class UnauthorizedApplicationContext {
|
|
let sharedContext: SharedAccountContextImpl
|
|
let account: UnauthorizedAccount
|
|
|
|
let rootController: AuthorizationSequenceController
|
|
|
|
let isReady = Promise<Bool>()
|
|
|
|
var authorizationCompleted: Bool = false
|
|
|
|
private var serviceNotificationEventsDisposable: Disposable?
|
|
|
|
init(apiId: Int32, apiHash: String, sharedContext: SharedAccountContextImpl, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])) {
|
|
self.sharedContext = sharedContext
|
|
self.account = account
|
|
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
|
|
|
var authorizationCompleted: (() -> Void)?
|
|
|
|
self.rootController = AuthorizationSequenceController(sharedContext: sharedContext, account: account, otherAccountPhoneNumbers: otherAccountPhoneNumbers, presentationData: presentationData, openUrl: sharedContext.applicationBindings.openUrl, apiId: apiId, apiHash: apiHash, authorizationCompleted: {
|
|
authorizationCompleted?()
|
|
})
|
|
|
|
authorizationCompleted = { [weak self] in
|
|
self?.authorizationCompleted = true
|
|
}
|
|
|
|
self.isReady.set(self.rootController.ready.get())
|
|
|
|
account.shouldBeServiceTaskMaster.set(sharedContext.applicationBindings.applicationInForeground |> map { value -> AccountServiceTaskMasterMode in
|
|
if value {
|
|
return .always
|
|
} else {
|
|
return .never
|
|
}
|
|
})
|
|
|
|
DeviceAccess.authorizeAccess(to: .cellularData, presentationData: sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
|
|
if let strongSelf = self {
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(c, in: .window(.root))
|
|
}
|
|
}, openSettings: {
|
|
sharedContext.applicationBindings.openSettings()
|
|
}, { result in
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: sharedContext.accountManager, permission: .cellularData, value: 0)
|
|
})
|
|
|
|
self.serviceNotificationEventsDisposable = (account.serviceNotificationEvents
|
|
|> deliverOnMainQueue).start(next: { [weak self] text in
|
|
if let strongSelf = self {
|
|
let presentationData = strongSelf.sharedContext.currentPresentationData.with { $0 }
|
|
let alertController = textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
|
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(alertController, in: .window(.root))
|
|
}
|
|
})
|
|
}
|
|
|
|
deinit {
|
|
self.serviceNotificationEventsDisposable?.dispose()
|
|
}
|
|
}
|
|
|
|
final class AuthorizedApplicationContext {
|
|
let sharedApplicationContext: SharedApplicationContext
|
|
let mainWindow: Window1
|
|
let lockedCoveringView: LockedWindowCoveringView
|
|
|
|
let context: AccountContextImpl
|
|
|
|
let rootController: TelegramRootController
|
|
let notificationController: NotificationContainerController
|
|
|
|
private var scheduledOpenNotificationSettings: Bool = false
|
|
private var scheduledOpenChatWithPeerId: (PeerId, MessageId?, Bool)?
|
|
private let scheduledCallPeerDisposable = MetaDisposable()
|
|
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 let appUpdateInfoDisposable = MetaDisposable()
|
|
|
|
private var inAppNotificationSettings: InAppNotificationSettings?
|
|
|
|
var passcodeController: PasscodeEntryController?
|
|
|
|
private var currentAppUpdateInfo: AppUpdateInfo?
|
|
private var currentTermsOfServiceUpdate: TermsOfServiceUpdate?
|
|
private var currentPermissionsController: PermissionController?
|
|
private var currentPermissionsState: PermissionState?
|
|
|
|
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, engine: self.context.engine)
|
|
|> 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(sharedApplicationContext: SharedApplicationContext, mainWindow: Window1, watchManagerArguments: Signal<WatchManagerArguments?, NoError>, context: AccountContextImpl, accountManager: AccountManager<TelegramAccountManagerTypes>, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) {
|
|
self.sharedApplicationContext = sharedApplicationContext
|
|
|
|
setupLegacyComponents(context: context)
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
self.mainWindow = mainWindow
|
|
self.lockedCoveringView = LockedWindowCoveringView(theme: presentationData.theme)
|
|
|
|
self.context = context
|
|
|
|
self.showCallsTab = showCallsTab
|
|
|
|
self.notificationController = NotificationContainerController(context: context)
|
|
|
|
self.mainWindow.previewThemeAccentColor = presentationData.theme.rootController.navigationBar.accentTextColor
|
|
self.mainWindow.previewThemeDarkBlur = presentationData.theme.rootController.keyboardColor == .dark
|
|
|
|
self.rootController = TelegramRootController(context: context)
|
|
|
|
self.rootController.globalOverlayControllersUpdated = { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
var hasContext = false
|
|
for controller in strongSelf.rootController.globalOverlayControllers {
|
|
if controller is ContextController {
|
|
hasContext = true
|
|
break
|
|
}
|
|
}
|
|
|
|
strongSelf.notificationController.updateIsTemporaryHidden(hasContext)
|
|
}
|
|
|
|
if KeyShortcutsController.isAvailable {
|
|
let keyShortcutsController = KeyShortcutsController { [weak self] f in
|
|
if let strongSelf = self, let appLockContext = strongSelf.context.sharedContext.appLockContext as? AppLockContextImpl {
|
|
let _ = (appLockContext.isCurrentlyLocked
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { locked in
|
|
guard !locked else {
|
|
return
|
|
}
|
|
if let tabController = strongSelf.rootController.rootTabController {
|
|
let selectedController = tabController.controllers[tabController.selectedIndex]
|
|
|
|
if let index = strongSelf.rootController.viewControllers.lastIndex(where: { controller in
|
|
guard let controller = controller as? ViewController else {
|
|
return false
|
|
}
|
|
if controller === tabController {
|
|
return false
|
|
}
|
|
switch controller.navigationPresentation {
|
|
case .master:
|
|
return true
|
|
default:
|
|
break
|
|
}
|
|
return false
|
|
}), let controller = strongSelf.rootController.viewControllers[index] as? ViewController {
|
|
if !f(controller) {
|
|
return
|
|
}
|
|
} else {
|
|
if !f(selectedController) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if let controller = strongSelf.rootController.topViewController as? ViewController, controller !== selectedController {
|
|
if !f(controller) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
strongSelf.mainWindow.forEachViewController(f)
|
|
})
|
|
}
|
|
}
|
|
context.keyShortcutsController = keyShortcutsController
|
|
}
|
|
|
|
if self.rootController.rootTabController == nil {
|
|
self.rootController.addRootControllers(showCallsTab: self.showCallsTab)
|
|
}
|
|
if let tabsController = self.rootController.viewControllers.first as? TabBarController, !tabsController.controllers.isEmpty, tabsController.selectedIndex >= 0 {
|
|
let controller = tabsController.controllers[tabsController.selectedIndex]
|
|
let combinedReady = combineLatest(tabsController.ready.get(), controller.ready.get())
|
|
|> map { $0 && $1 }
|
|
|> filter { $0 }
|
|
|> take(1)
|
|
self.isReady.set(combinedReady)
|
|
} else {
|
|
self.isReady.set(.single(true))
|
|
}
|
|
|
|
let accountId = context.account.id
|
|
self.loggedOutDisposable.set((context.account.loggedOut
|
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if value {
|
|
Logger.shared.log("ApplicationContext", "account logged out")
|
|
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: false).start()
|
|
if let strongSelf = self {
|
|
strongSelf.rootController.currentWindow?.forEachController { controller in
|
|
if let controller = controller as? TermsOfServiceController {
|
|
controller.dismiss()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
|
|
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]?.get(InAppNotificationSettings.self) {
|
|
let previousSettings = strongSelf.inAppNotificationSettings
|
|
strongSelf.inAppNotificationSettings = settings
|
|
if let previousSettings = previousSettings, previousSettings.displayNameOnLockscreen != settings.displayNameOnLockscreen {
|
|
reinitializedNotificationSettings()
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
|
|
let postbox = context.account.postbox
|
|
self.notificationMessagesDisposable.set((context.account.stateManager.notificationMessages
|
|
|> mapToSignal { messageList -> Signal<[([Message], PeerGroupId, Bool)], NoError> in
|
|
return postbox.transaction { transaction -> [([Message], PeerGroupId, Bool)] in
|
|
return messageList.filter { item in
|
|
guard let message = item.0.first else {
|
|
return false
|
|
}
|
|
let inclusion = transaction.getPeerChatListInclusion(message.id.peerId)
|
|
if case .notIncluded = inclusion {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|> deliverOn(Queue.mainQueue())).start(next: { [weak self] messageList in
|
|
if messageList.isEmpty {
|
|
return
|
|
}
|
|
|
|
if let strongSelf = self, let (messages, _, notify) = messageList.last, let firstMessage = messages.first {
|
|
if UIApplication.shared.applicationState == .active {
|
|
var chatIsVisible = false
|
|
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl, topController.traceVisibility() {
|
|
if topController.chatLocation.peerId == firstMessage.id.peerId {
|
|
chatIsVisible = true
|
|
}
|
|
}
|
|
|
|
if !notify {
|
|
chatIsVisible = true
|
|
}
|
|
|
|
if !chatIsVisible {
|
|
strongSelf.mainWindow.forEachViewController({ controller in
|
|
if let controller = controller as? ChatControllerImpl, 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 let appLockContext = strongSelf.context.sharedContext.appLockContext as? AppLockContextImpl {
|
|
let _ = (appLockContext.isCurrentlyLocked
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { locked in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
guard !locked else {
|
|
return
|
|
}
|
|
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 firstMessage.id.peerId == context.account.peerId, !firstMessage.flags.contains(.WasScheduled) {
|
|
} else {
|
|
if inAppNotificationSettings.playSounds {
|
|
serviceSoundManager.playIncomingMessageSound()
|
|
}
|
|
if inAppNotificationSettings.vibrate {
|
|
serviceSoundManager.playVibrationSound()
|
|
}
|
|
}
|
|
}
|
|
if let forwardInfo = firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
|
return
|
|
}
|
|
for media in firstMessage.media {
|
|
if let action = media as? TelegramMediaAction {
|
|
if case .messageAutoremoveTimeoutUpdated = action.action {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if chatIsVisible {
|
|
return
|
|
}
|
|
|
|
if inAppNotificationSettings.displayPreviews {
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
strongSelf.notificationController.enqueue(ChatMessageNotificationItem(context: strongSelf.context, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, 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
|
|
}, excludeNavigationSubControllers: true)
|
|
|
|
if foundOverlay {
|
|
return true
|
|
}
|
|
|
|
if let topController = strongSelf.rootController.topViewController as? ViewController, isInlineControllerForChatNotificationOverlayPresentation(topController) {
|
|
return true
|
|
}
|
|
|
|
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl, 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? ChatControllerImpl, case .peer(firstMessage.id.peerId) = controller.chatLocation {
|
|
return true
|
|
}
|
|
}
|
|
|
|
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
|
|
|
|
var processed = false
|
|
for media in firstMessage.media {
|
|
if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action {
|
|
strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController)
|
|
processed = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !processed {
|
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId)))
|
|
}
|
|
}
|
|
return false
|
|
}, expandAction: { expandData in
|
|
if let strongSelf = self {
|
|
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId), mode: .overlay(strongSelf.rootController))
|
|
//chatController.navigation_setNavigationController(strongSelf.rootController)
|
|
chatController.presentationArguments = ChatControllerOverlayPresentationData(expandData: expandData())
|
|
//strongSelf.rootController.pushViewController(chatController)
|
|
(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(presentationData: presentationData, 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 _ = (strongSelf.context.engine.accountData.acceptTermsOfService(id: termsOfServiceUpdate.id)
|
|
|> deliverOnMainQueue).start(completed: {
|
|
controller?.dismiss()
|
|
if let strongSelf = self, let botName = botName {
|
|
strongSelf.termsOfServiceProceedToBotDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: botName, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peer in
|
|
if let strongSelf = self, let peer = peer {
|
|
self?.rootController.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peer.id)))
|
|
}
|
|
}))
|
|
}
|
|
})
|
|
}
|
|
|
|
declineImpl = { [weak controller] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let accountId = strongSelf.context.account.id
|
|
let accountManager = strongSelf.context.sharedContext.accountManager
|
|
let _ = (strongSelf.context.engine.auth.deleteAccount()
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
|
}, completed: {
|
|
controller?.dismiss()
|
|
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: true).start()
|
|
})
|
|
}
|
|
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
|
}
|
|
}))
|
|
|
|
self.appUpdateInfoDisposable.set((context.account.stateManager.appUpdateInfo
|
|
|> deliverOnMainQueue).start(next: { [weak self] appUpdateInfo in
|
|
guard let strongSelf = self, strongSelf.currentAppUpdateInfo != appUpdateInfo else {
|
|
return
|
|
}
|
|
|
|
strongSelf.currentAppUpdateInfo = appUpdateInfo
|
|
if let appUpdateInfo = appUpdateInfo {
|
|
let controller = updateInfoController(context: strongSelf.context, appUpdateInfo: appUpdateInfo)
|
|
strongSelf.mainWindow.present(controller, on: .update)
|
|
}
|
|
}))
|
|
|
|
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.permissionWarningKey(permission: .contacts)!), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .notifications)!), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .cellularData)!))
|
|
|> deliverOnMainQueue).start(next: { [weak self] required, splitTest, position, contactsPermissionWarningNotice, notificationsPermissionWarningNotice, cellularDataPermissionWarningNotice in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let contactsTimestamp = contactsPermissionWarningNotice.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
|
|
let notificationsTimestamp = notificationsPermissionWarningNotice.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
|
|
let cellularDataTimestamp = cellularDataPermissionWarningNotice.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
|
|
if contactsTimestamp == nil, case .requestable = required.0.status {
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .contacts, value: 1)
|
|
}
|
|
if notificationsTimestamp == nil, case .requestable = required.1.status {
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .notifications, value: 1)
|
|
}
|
|
|
|
let config = splitTest.configuration
|
|
var order = config.order
|
|
if !order.contains(.cellularData) {
|
|
order.append(.cellularData)
|
|
}
|
|
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 .cellularData:
|
|
if case .denied = required.2.status, cellularDataTimestamp != 0 {
|
|
requestedPermissions.append((required.2, true))
|
|
}
|
|
case .siri:
|
|
if case .requestable = required.3.status {
|
|
requestedPermissions.append((required.3, 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(.permission(state), animated: didAppear)
|
|
controller.proceed = { resolved in
|
|
permissionsPosition.set(position + 1)
|
|
switch state {
|
|
case .contacts:
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .contacts, value: 0)
|
|
case .notifications:
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .notifications, value: 0)
|
|
case .cellularData:
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .cellularData, value: 0)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
if !didAppear {
|
|
Queue.mainQueue().after(0.15, {
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}
|
|
} else {
|
|
if strongSelf.currentPermissionsState != state {
|
|
strongSelf.currentPermissionsState = state
|
|
switch state {
|
|
case .contacts:
|
|
splitTest.addEvent(.ContactsRequest)
|
|
DeviceAccess.authorizeAccess(to: .contacts, presentationData: context.sharedContext.currentPresentationData.with { $0 }, { result in
|
|
if result {
|
|
splitTest.addEvent(.ContactsAllowed)
|
|
} else {
|
|
splitTest.addEvent(.ContactsDenied)
|
|
}
|
|
permissionsPosition.set(position + 1)
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .contacts, value: 0)
|
|
})
|
|
case .notifications:
|
|
splitTest.addEvent(.NotificationsRequest)
|
|
DeviceAccess.authorizeAccess(to: .notifications, registerForNotifications: { result in
|
|
context.sharedContext.applicationBindings.registerForNotifications(result)
|
|
}, { result in
|
|
if result {
|
|
splitTest.addEvent(.NotificationsAllowed)
|
|
} else {
|
|
splitTest.addEvent(.NotificationsDenied)
|
|
}
|
|
permissionsPosition.set(position + 1)
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .notifications, value: 0)
|
|
})
|
|
case .cellularData:
|
|
DeviceAccess.authorizeAccess(to: .cellularData, presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
|
|
if let strongSelf = self {
|
|
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(c, in: .window(.root))
|
|
}
|
|
}, openSettings: {
|
|
context.sharedContext.applicationBindings.openSettings()
|
|
}, { result in
|
|
permissionsPosition.set(position + 1)
|
|
ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .cellularData, value: 0)
|
|
})
|
|
case .siri:
|
|
DeviceAccess.authorizeAccess(to: .siri, requestSiriAuthorization: { completion in
|
|
return context.sharedContext.applicationBindings.requestSiriAuthorization(completion)
|
|
}, { result in
|
|
permissionsPosition.set(position + 1)
|
|
})
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if let controller = strongSelf.currentPermissionsController {
|
|
strongSelf.currentPermissionsController = nil
|
|
controller.dismiss(completion: {})
|
|
}
|
|
strongSelf.currentPermissionsState = nil
|
|
}
|
|
}))
|
|
}
|
|
|
|
self.displayAlertsDisposable = (context.account.stateManager.displayAlerts
|
|
|> deliverOnMainQueue).start(next: { [weak self] alerts in
|
|
if let strongSelf = self {
|
|
for (text, isDropAuth) in alerts {
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let actions: [TextAlertAction]
|
|
if isDropAuth {
|
|
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.LogoutOptions_LogOut, action: {
|
|
if let strongSelf = self {
|
|
let _ = logoutFromAccount(id: strongSelf.context.account.id, accountManager: strongSelf.context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).start()
|
|
}
|
|
})]
|
|
} else {
|
|
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
|
|
}
|
|
let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions)
|
|
(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)
|
|
}
|
|
})
|
|
|
|
let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:])
|
|
self.context.account.importableContacts.set(self.context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings])
|
|
|> mapToSignal { preferences -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in
|
|
let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? .defaultSettings
|
|
if settings.synchronizeContacts {
|
|
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.rootController.keyboardColor == .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 = CallListSettings.defaultSettings.showTab
|
|
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings]?.get(CallListSettings.self) {
|
|
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 = WatchManagerImpl(arguments: arguments)
|
|
strongSelf.context.watchManager = watchManager
|
|
|
|
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? ChatControllerImpl, case .peer(messageId.peerId) = controller.chatLocation {
|
|
chatIsVisible = true
|
|
}
|
|
|
|
let navigateToMessage = {
|
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil)))
|
|
}
|
|
|
|
if chatIsVisible {
|
|
navigateToMessage()
|
|
} else {
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = textAlertController(context: strongSelf.context, 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)
|
|
}
|
|
}
|
|
}))
|
|
})
|
|
|
|
self.rootController.setForceInCallStatusBar((self.context.sharedContext as! SharedAccountContextImpl).currentCallStatusBarNode)
|
|
if let groupCallController = self.context.sharedContext.currentGroupCallController as? VoiceChatController {
|
|
if let overlayController = groupCallController.currentOverlayController {
|
|
groupCallController.parentNavigationController = self.rootController
|
|
self.rootController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
self.scheduledCallPeerDisposable.dispose()
|
|
}
|
|
|
|
func openNotificationSettings() {
|
|
if self.rootController.rootTabController != nil {
|
|
self.rootController.pushViewController(notificationsAndSoundsController(context: self.context, exceptionsList: nil))
|
|
} else {
|
|
self.scheduledOpenNotificationSettings = true
|
|
}
|
|
}
|
|
|
|
func startCall(peerId: PeerId, isVideo: Bool) {
|
|
guard let appLockContext = self.context.sharedContext.appLockContext as? AppLockContextImpl else {
|
|
return
|
|
}
|
|
self.scheduledCallPeerDisposable.set((appLockContext.isCurrentlyLocked
|
|
|> filter {
|
|
!$0
|
|
}
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false)
|
|
}))
|
|
}
|
|
|
|
func openChatWithPeerId(peerId: PeerId, messageId: MessageId? = nil, activateInput: Bool = false) {
|
|
var visiblePeerId: PeerId?
|
|
if let controller = self.rootController.topViewController as? ChatControllerImpl, case let .peer(peerId) = controller.chatLocation {
|
|
visiblePeerId = peerId
|
|
}
|
|
|
|
if visiblePeerId != peerId || messageId != nil {
|
|
if self.rootController.rootTabController != nil {
|
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), subject: messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }, activateInput: activateInput))
|
|
} else {
|
|
self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput)
|
|
}
|
|
}
|
|
}
|
|
|
|
func openUrl(_ url: URL) {
|
|
if self.rootController.rootTabController != nil {
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url.absoluteString, forceExternal: false, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in
|
|
self?.rootController.view.endEditing(true)
|
|
})
|
|
} else {
|
|
self.scheduledOpenExternalUrl = url
|
|
}
|
|
}
|
|
|
|
func openRootSearch() {
|
|
self.rootController.openChatsController(activateSearch: true)
|
|
}
|
|
|
|
func openRootCompose() {
|
|
self.rootController.openRootCompose()
|
|
}
|
|
|
|
func openRootCamera() {
|
|
self.rootController.openRootCamera()
|
|
}
|
|
|
|
func switchAccount() {
|
|
let _ = (activeAccountsAndPeers(context: self.context)
|
|
|> take(1)
|
|
|> map { primaryAndAccounts -> (AccountContext, EnginePeer, Int32)? in
|
|
return primaryAndAccounts.1.first
|
|
}
|
|
|> map { accountAndPeer -> AccountContext? in
|
|
if let (context, _, _) = accountAndPeer {
|
|
return context
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|> deliverOnMainQueue).start(next: { [weak self] context in
|
|
guard let strongSelf = self, let context = context else {
|
|
return
|
|
}
|
|
strongSelf.context.sharedContext.switchToAccount(id: context.account.id, fromSettingsController: nil, withChatListController: nil)
|
|
})
|
|
}
|
|
|
|
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()
|
|
}).flatMap(applyScreenshotEffectToImage)
|
|
self.lockedCoveringView.updateSnapshot(image)
|
|
} else {
|
|
self.lockedCoveringView.updateSnapshot(nil)
|
|
}
|
|
}
|
|
}
|