Swiftgram/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift
2019-08-02 02:26:53 +03:00

924 lines
46 KiB
Swift

import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
import TelegramPresentationData
import TelegramCallsUI
import TelegramUIPreferences
import AccountContext
private enum CallStatusText: Equatable {
case none
case inProgress(Double?)
}
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
let tokens = Bag<Void>()
var isEmpty: Bool {
return self.tokens.isEmpty && self.subscribers.isEmpty
}
}
public final class AccountWithInfo: Equatable {
public let account: Account
public let peer: Peer
init(account: Account, peer: Peer) {
self.account = account
self.peer = peer
}
public static func ==(lhs: AccountWithInfo, rhs: AccountWithInfo) -> Bool {
if lhs.account !== rhs.account {
return false
}
if !arePeersEqual(lhs.peer, rhs.peer) {
return false
}
return true
}
}
private func preFetchedLegacyResourcePath(basePath: String, resource: MediaResource, cache: LegacyCache) -> String? {
if let resource = resource as? CloudDocumentMediaResource {
let videoPath = "\(basePath)/Documents/video/remote\(String(resource.fileId, radix: 16)).mov"
if FileManager.default.fileExists(atPath: videoPath) {
return videoPath
}
let fileName = resource.fileName?.replacingOccurrences(of: "/", with: "_") ?? "file"
return pathFromLegacyFile(basePath: basePath, fileId: resource.fileId, isLocal: false, fileName: fileName)
} else if let resource = resource as? CloudFileMediaResource {
return cache.path(forCachedData: "\(resource.datacenterId)_\(resource.volumeId)_\(resource.localId)_\(resource.secret)")
}
return nil
}
private struct AccountAttributes: Equatable {
let sortIndex: Int32
let isTestingEnvironment: Bool
let backupData: AccountBackupData?
}
private enum AddedAccountResult {
case upgrading(Float)
case ready(AccountRecordId, Account?, Int32)
}
private enum AddedAccountsResult {
case upgrading(Float)
case ready([(AccountRecordId, Account?, Int32)])
}
private var testHasInstance = false
public final class SharedAccountContextImpl: SharedAccountContext {
let mainWindow: Window1?
public let applicationBindings: TelegramApplicationBindings
public let basePath: String
public let accountManager: AccountManager
private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void
private let apsNotificationToken: Signal<Data?, NoError>
private let voipNotificationToken: Signal<Data?, NoError>
private var activeAccountsValue: (primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?)?
private let activeAccountsPromise = Promise<(primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?)>()
public var activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?), NoError> {
return self.activeAccountsPromise.get()
}
private let managedAccountDisposables = DisposableDict<AccountRecordId>()
private let activeAccountsWithInfoPromise = Promise<(primary: AccountRecordId?, accounts: [AccountWithInfo])>()
public var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> {
return self.activeAccountsWithInfoPromise.get()
}
private var activeUnauthorizedAccountValue: UnauthorizedAccount?
private let activeUnauthorizedAccountPromise = Promise<UnauthorizedAccount?>()
public var activeUnauthorizedAccount: Signal<UnauthorizedAccount?, NoError> {
return self.activeUnauthorizedAccountPromise.get()
}
private let registeredNotificationTokensDisposable = MetaDisposable()
public let mediaManager: MediaManager
public let contactDataManager: DeviceContactDataManager?
let locationManager: DeviceLocationManager?
public var callManager: PresentationCallManager?
private var callDisposable: Disposable?
private var callStateDisposable: Disposable?
private var currentCallStatusText: CallStatusText = .none
private var currentCallStatusTextTimer: SwiftSignalKit.Timer?
private var callController: CallController?
public let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
public var immediateHasOngoingCall: Bool {
return self.immediateHasOngoingCallValue.with { $0 }
}
private var hasOngoingCallDisposable: Disposable?
private var accountUserInterfaceInUseContexts: [AccountRecordId: AccountUserInterfaceInUseContext] = [:]
var switchingData: (settingsController: (SettingsController & ViewController)?, chatListController: ChatListController?, chatListBadge: String?) = (nil, nil, nil)
private let _currentPresentationData: Atomic<PresentationData>
public var currentPresentationData: Atomic<PresentationData> {
return self._currentPresentationData
}
private let _presentationData = Promise<PresentationData>()
public var presentationData: Signal<PresentationData, NoError> {
return self._presentationData.get()
}
private let presentationDataDisposable = MetaDisposable()
public let currentInAppNotificationSettings: Atomic<InAppNotificationSettings>
private var inAppNotificationSettingsDisposable: Disposable?
public let currentAutomaticMediaDownloadSettings: Atomic<MediaAutoDownloadSettings>
private let _automaticMediaDownloadSettings = Promise<MediaAutoDownloadSettings>()
public var automaticMediaDownloadSettings: Signal<MediaAutoDownloadSettings, NoError> {
return self._automaticMediaDownloadSettings.get()
}
public let currentMediaInputSettings: Atomic<MediaInputSettings>
private var mediaInputSettingsDisposable: Disposable?
private let automaticMediaDownloadSettingsDisposable = MetaDisposable()
private var immediateExperimentalUISettingsValue = Atomic<ExperimentalUISettings>(value: ExperimentalUISettings.defaultSettings)
public var immediateExperimentalUISettings: ExperimentalUISettings {
return self.immediateExperimentalUISettingsValue.with { $0 }
}
private var experimentalUISettingsDisposable: Disposable?
public var presentGlobalController: (ViewController, Any?) -> Void = { _, _ in }
public var presentCrossfadeController: () -> Void = {}
private let displayUpgradeProgress: (Float?) -> Void
public init(mainWindow: Window1?, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, legacyCache: LegacyCache?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) {
assert(Queue.mainQueue().isCurrent())
precondition(!testHasInstance)
testHasInstance = true
self.mainWindow = mainWindow
self.applicationBindings = applicationBindings
self.basePath = basePath
self.accountManager = accountManager
self.navigateToChatImpl = navigateToChat
self.displayUpgradeProgress = displayUpgradeProgress
self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal<CachedMediaResourceRepresentationResult, NoError> in
return fetchCachedSharedResourceRepresentation(accountManager: accountManager, resource: resource, representation: representation)
}
self.apsNotificationToken = apsNotificationToken
self.voipNotificationToken = voipNotificationToken
self.mediaManager = MediaManager(accountManager: accountManager, inForeground: applicationBindings.applicationInForeground)
if applicationBindings.isMainApp {
self.locationManager = DeviceLocationManager(queue: Queue.mainQueue())
self.contactDataManager = DeviceContactDataManager()
} else {
self.locationManager = nil
self.contactDataManager = nil
}
self._currentPresentationData = Atomic(value: initialPresentationDataAndSettings.presentationData)
self.currentAutomaticMediaDownloadSettings = Atomic(value: initialPresentationDataAndSettings.automaticMediaDownloadSettings)
self.currentMediaInputSettings = Atomic(value: initialPresentationDataAndSettings.mediaInputSettings)
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
self._presentationData.set(.single(initialPresentationDataAndSettings.presentationData)
|> then(
updatedPresentationData(accountManager: self.accountManager, applicationInForeground: self.applicationBindings.applicationInForeground)
))
self._automaticMediaDownloadSettings.set(.single(initialPresentationDataAndSettings.automaticMediaDownloadSettings)
|> then(accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])
|> map { sharedData in
let autodownloadSettings: AutodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let automaticDownloadSettings: MediaAutoDownloadSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings] as? MediaAutoDownloadSettings ?? .defaultSettings
return automaticDownloadSettings.updatedWithAutodownloadSettings(autodownloadSettings)
}
))
self.presentationDataDisposable.set((self.presentationData
|> deliverOnMainQueue).start(next: { [weak self] next in
if let strongSelf = self {
var stringsUpdated = false
var themeUpdated = false
var themeNameUpdated = false
let _ = strongSelf.currentPresentationData.modify { current in
if next.strings !== current.strings {
stringsUpdated = true
}
if next.theme !== current.theme {
themeUpdated = true
}
if next.theme.name != current.theme.name {
themeNameUpdated = true
}
return next
}
if stringsUpdated {
updateLegacyLocalization(strings: next.strings)
}
if themeUpdated {
updateLegacyTheme()
}
if themeNameUpdated {
strongSelf.presentCrossfadeController()
}
}
}))
self.inAppNotificationSettingsDisposable = (self.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 _ = strongSelf.currentInAppNotificationSettings.swap(settings)
}
}
})
self.mediaInputSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaInputSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
if let strongSelf = self {
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaInputSettings] as? MediaInputSettings {
let _ = strongSelf.currentMediaInputSettings.swap(settings)
}
}
})
let immediateExperimentalUISettingsValue = self.immediateExperimentalUISettingsValue
let _ = immediateExperimentalUISettingsValue.swap(initialPresentationDataAndSettings.experimentalUISettings)
self.experimentalUISettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.experimentalUISettings])
|> deliverOnMainQueue).start(next: { sharedData in
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings {
let _ = immediateExperimentalUISettingsValue.swap(settings)
}
})
let _ = self.contactDataManager?.personNameDisplayOrder().start(next: { order in
let _ = updateContactSettingsInteractively(accountManager: accountManager, { settings in
var settings = settings
settings.nameDisplayOrder = order
return settings
}).start()
})
self.automaticMediaDownloadSettingsDisposable.set(self._automaticMediaDownloadSettings.get().start(next: { [weak self] next in
if let strongSelf = self {
let _ = strongSelf.currentAutomaticMediaDownloadSettings.swap(next)
}
}))
let startTime = CFAbsoluteTimeGetCurrent()
let differenceDisposable = MetaDisposable()
let _ = (accountManager.accountRecords()
|> map { view -> (AccountRecordId?, [AccountRecordId: AccountAttributes], (AccountRecordId, Bool)?) in
print("SharedAccountContextImpl: records appeared in \(CFAbsoluteTimeGetCurrent() - startTime)")
var result: [AccountRecordId: AccountAttributes] = [:]
for record in view.records {
let isLoggedOut = record.attributes.contains(where: { attribute in
return attribute is LoggedOutAccountAttribute
})
if isLoggedOut {
continue
}
let isTestingEnvironment = record.attributes.contains(where: { attribute in
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
return true
} else {
return false
}
})
var backupData: AccountBackupData?
var sortIndex: Int32 = 0
for attribute in record.attributes {
if let attribute = attribute as? AccountSortOrderAttribute {
sortIndex = attribute.order
} else if let attribute = attribute as? AccountBackupDataAttribute {
backupData = attribute.data
}
}
result[record.id] = AccountAttributes(sortIndex: sortIndex, isTestingEnvironment: isTestingEnvironment, backupData: backupData)
}
let authRecord: (AccountRecordId, Bool)? = view.currentAuthAccount.flatMap({ authAccount in
let isTestingEnvironment = authAccount.attributes.contains(where: { attribute in
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
return true
} else {
return false
}
})
return (authAccount.id, isTestingEnvironment)
})
return (view.currentRecord?.id, result, authRecord)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.0 != rhs.0 {
return false
}
if lhs.1 != rhs.1 {
return false
}
if lhs.2?.0 != rhs.2?.0 {
return false
}
if lhs.2?.1 != rhs.2?.1 {
return false
}
return true
})
|> deliverOnMainQueue).start(next: { primaryId, records, authRecord in
var addedSignals: [Signal<AddedAccountResult, NoError>] = []
var addedAuthSignal: Signal<UnauthorizedAccount?, NoError> = .single(nil)
for (id, attributes) in records {
if self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == id}) == nil {
addedSignals.append(accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: !applicationBindings.isMainApp, rootPath: rootPath, beginWithTestingEnvironment: attributes.isTestingEnvironment, backupData: attributes.backupData, auxiliaryMethods: telegramAccountAuxiliaryMethods)
|> map { result -> AddedAccountResult in
switch result {
case let .authorized(account):
setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia, preFetchedResourcePath: { resource in
if let legacyBasePath = legacyBasePath, let legacyCache = legacyCache {
return preFetchedLegacyResourcePath(basePath: legacyBasePath, resource: resource, cache: legacyCache)
} else {
return nil
}
})
return .ready(id, account, attributes.sortIndex)
case let .upgrading(progress):
return .upgrading(progress)
default:
return .ready(id, nil, attributes.sortIndex)
}
})
}
}
if let authRecord = authRecord, authRecord.0 != self.activeAccountsValue?.currentAuth?.id {
addedAuthSignal = accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: authRecord.0, encryptionParameters: encryptionParameters, supplementary: !applicationBindings.isMainApp, rootPath: rootPath, beginWithTestingEnvironment: authRecord.1, backupData: nil, auxiliaryMethods: telegramAccountAuxiliaryMethods)
|> mapToSignal { result -> Signal<UnauthorizedAccount?, NoError> in
switch result {
case let .unauthorized(account):
return .single(account)
case .upgrading:
return .complete()
default:
return .single(nil)
}
}
}
let mappedAddedAccounts = combineLatest(queue: .mainQueue(), addedSignals)
|> map { results -> AddedAccountsResult in
var readyAccounts: [(AccountRecordId, Account?, Int32)] = []
var totalProgress: Float = 0.0
var hasItemsWithProgress = false
for result in results {
switch result {
case let .ready(id, account, sortIndex):
readyAccounts.append((id, account, sortIndex))
totalProgress += 1.0
case let .upgrading(progress):
hasItemsWithProgress = true
totalProgress += progress
}
}
if hasItemsWithProgress, !results.isEmpty {
return .upgrading(totalProgress / Float(results.count))
} else {
return .ready(readyAccounts)
}
}
differenceDisposable.set((combineLatest(queue: .mainQueue(), mappedAddedAccounts, addedAuthSignal)
|> deliverOnMainQueue).start(next: { mappedAddedAccounts, authAccount in
print("SharedAccountContextImpl: accounts processed in \(CFAbsoluteTimeGetCurrent() - startTime)")
var addedAccounts: [(AccountRecordId, Account?, Int32)] = []
switch mappedAddedAccounts {
case let .upgrading(progress):
self.displayUpgradeProgress(progress)
return
case let .ready(value):
addedAccounts = value
}
self.displayUpgradeProgress(nil)
var hadUpdates = false
if self.activeAccountsValue == nil {
self.activeAccountsValue = (nil, [], nil)
hadUpdates = true
}
struct AccountPeerKey: Hashable {
let peerId: PeerId
let isTestingEnvironment: Bool
}
var existingAccountPeerKeys = Set<AccountPeerKey>()
for accountRecord in addedAccounts {
if let account = accountRecord.1 {
if existingAccountPeerKeys.contains(AccountPeerKey(peerId: account.peerId, isTestingEnvironment: account.testingEnvironment)) {
let _ = accountManager.transaction({ transaction in
transaction.updateRecord(accountRecord.0, { _ in
return nil
})
}).start()
} else {
existingAccountPeerKeys.insert(AccountPeerKey(peerId: account.peerId, isTestingEnvironment: account.testingEnvironment))
if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == account.id }) {
self.activeAccountsValue?.accounts.remove(at: index)
self.managedAccountDisposables.set(nil, forKey: account.id)
assertionFailure()
}
self.activeAccountsValue!.accounts.append((account.id, account, accountRecord.2))
self.managedAccountDisposables.set(self.updateAccountBackupData(account: account).start(), forKey: account.id)
account.resetStateManagement()
hadUpdates = true
}
} else {
let _ = accountManager.transaction({ transaction in
transaction.updateRecord(accountRecord.0, { _ in
return nil
})
}).start()
}
}
var removedIds: [AccountRecordId] = []
for id in self.activeAccountsValue!.accounts.map({ $0.0 }) {
if records[id] == nil {
removedIds.append(id)
}
}
for id in removedIds {
hadUpdates = true
if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == id }) {
self.activeAccountsValue?.accounts.remove(at: index)
self.managedAccountDisposables.set(nil, forKey: id)
}
}
var primary: Account?
if let primaryId = primaryId {
if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == primaryId }) {
primary = self.activeAccountsValue?.accounts[index].1
}
}
if primary == nil && !self.activeAccountsValue!.accounts.isEmpty {
primary = self.activeAccountsValue!.accounts.first?.1
}
if primary !== self.activeAccountsValue!.primary {
hadUpdates = true
self.activeAccountsValue!.primary?.postbox.clearCaches()
self.activeAccountsValue!.primary?.resetCachedData()
self.activeAccountsValue!.primary = primary
}
if self.activeAccountsValue!.currentAuth?.id != authRecord?.0 {
hadUpdates = true
self.activeAccountsValue!.currentAuth?.postbox.clearCaches()
self.activeAccountsValue!.currentAuth = nil
}
if let authAccount = authAccount {
hadUpdates = true
self.activeAccountsValue!.currentAuth = authAccount
}
if hadUpdates {
self.activeAccountsValue!.accounts.sort(by: { $0.2 < $1.2 })
self.activeAccountsPromise.set(.single(self.activeAccountsValue!))
}
if self.activeAccountsValue!.primary == nil && self.activeAccountsValue!.currentAuth == nil {
self.beginNewAuth(testingEnvironment: false)
}
}))
})
self.activeAccountsWithInfoPromise.set(self.activeAccounts
|> mapToSignal { primary, accounts, _ -> Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> in
return combineLatest(accounts.map { _, account, _ -> Signal<AccountWithInfo?, NoError> in
let peerViewKey: PostboxViewKey = .peer(peerId: account.peerId, components: [])
return account.postbox.combinedView(keys: [peerViewKey])
|> map { view -> AccountWithInfo? in
guard let peerView = view.views[peerViewKey] as? PeerView, let peer = peerView.peers[peerView.peerId] else {
return nil
}
return AccountWithInfo(account: account, peer: peer)
}
|> distinctUntilChanged
})
|> map { accountsWithInfo -> (primary: AccountRecordId?, accounts: [AccountWithInfo]) in
var accountsWithInfoResult: [AccountWithInfo] = []
for info in accountsWithInfo {
if let info = info {
accountsWithInfoResult.append(info)
}
}
return (primary?.id, accountsWithInfoResult)
}
})
if let mainWindow = mainWindow, applicationBindings.isMainApp {
let callManager = PresentationCallManager(accountManager: self.accountManager, getDeviceAccessData: {
return (self.currentPresentationData.with { $0 }, { [weak self] c, a in
self?.presentGlobalController(c, a)
}, {
applicationBindings.openSettings()
})
}, audioSession: self.mediaManager.audioSession, activeAccounts: self.activeAccounts |> map { _, accounts, _ in
return Array(accounts.map({ $0.1 }))
})
self.callManager = callManager
self.callDisposable = (callManager.currentCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if call !== strongSelf.callController?.call {
strongSelf.callController?.dismiss()
strongSelf.callController = nil
strongSelf.hasOngoingCall.set(false)
if let call = call {
mainWindow.hostView.containerView.endEditing(true)
let callController = CallController(sharedContext: strongSelf, account: call.account, call: call)
strongSelf.callController = callController
strongSelf.mainWindow?.present(callController, on: .calls)
strongSelf.callState.set(call.state
|> map(Optional.init))
strongSelf.hasOngoingCall.set(true)
setNotificationCall(call)
} else {
strongSelf.callState.set(.single(nil))
strongSelf.hasOngoingCall.set(false)
setNotificationCall(nil)
}
}
}
})
self.callStateDisposable = (self.callState.get()
|> deliverOnMainQueue).start(next: { [weak self] state in
if let strongSelf = self {
let resolvedText: CallStatusText
if let state = state {
switch state {
case .connecting, .requesting, .terminating, .ringing, .waiting:
resolvedText = .inProgress(nil)
case .terminated:
resolvedText = .none
case let .active(timestamp, _, _):
resolvedText = .inProgress(timestamp)
}
} else {
resolvedText = .none
}
if strongSelf.currentCallStatusText != resolvedText {
strongSelf.currentCallStatusText = resolvedText
var referenceTimestamp: Double?
if case let .inProgress(timestamp) = resolvedText, let concreteTimestamp = timestamp {
referenceTimestamp = concreteTimestamp
}
if let _ = referenceTimestamp {
if strongSelf.currentCallStatusTextTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: {
if let strongSelf = self {
strongSelf.updateStatusBarText()
}
}, queue: Queue.mainQueue())
strongSelf.currentCallStatusTextTimer = timer
timer.start()
}
} else {
strongSelf.currentCallStatusTextTimer?.invalidate()
strongSelf.currentCallStatusTextTimer = nil
}
strongSelf.updateStatusBarText()
}
}
})
mainWindow.inCallNavigate = { [weak self] in
if let strongSelf = self, let callController = strongSelf.callController {
if callController.isNodeLoaded && callController.view.superview == nil {
mainWindow.hostView.containerView.endEditing(true)
mainWindow.present(callController, on: .calls)
}
}
}
} else {
self.callManager = nil
}
let immediateHasOngoingCallValue = self.immediateHasOngoingCallValue
self.hasOngoingCallDisposable = self.hasOngoingCall.get().start(next: { value in
let _ = immediateHasOngoingCallValue.swap(value)
})
let _ = managedCleanupAccounts(networkArguments: networkArguments, accountManager: self.accountManager, rootPath: rootPath, auxiliaryMethods: telegramAccountAuxiliaryMethods, encryptionParameters: encryptionParameters).start()
self.updateNotificationTokensRegistration()
}
deinit {
assertionFailure("SharedAccountContextImpl is not supposed to be deallocated")
self.registeredNotificationTokensDisposable.dispose()
self.presentationDataDisposable.dispose()
self.automaticMediaDownloadSettingsDisposable.dispose()
self.inAppNotificationSettingsDisposable?.dispose()
self.mediaInputSettingsDisposable?.dispose()
self.callDisposable?.dispose()
self.callStateDisposable?.dispose()
self.currentCallStatusTextTimer?.invalidate()
}
private func updateAccountBackupData(account: Account) -> Signal<Never, NoError> {
return accountBackupData(postbox: account.postbox)
|> mapToSignal { backupData -> Signal<Never, NoError> in
guard let backupData = backupData else {
return .complete()
}
return self.accountManager.transaction { transaction -> Void in
transaction.updateRecord(account.id, { record in
guard let record = record else {
return nil
}
var attributes = record.attributes.filter({ !($0 is AccountBackupDataAttribute) })
attributes.append(AccountBackupDataAttribute(data: backupData))
return AccountRecord(id: record.id, attributes: attributes, temporarySessionId: record.temporarySessionId)
})
}
|> ignoreValues
}
}
public func updateNotificationTokensRegistration() {
let sandbox: Bool
#if DEBUG
sandbox = true
#else
sandbox = false
#endif
let settings = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
|> map { sharedData -> (allAccounts: Bool, includeMuted: Bool) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
return (settings.displayNotificationsFromAllAccounts, settings.totalUnreadCountDisplayStyle == .raw)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.allAccounts != rhs.allAccounts {
return false
}
if lhs.includeMuted != rhs.includeMuted {
return false
}
return true
})
self.registeredNotificationTokensDisposable.set((combineLatest(queue: .mainQueue(), settings, self.activeAccounts)
|> mapToSignal { settings, activeAccountsAndInfo -> Signal<Never, NoError> in
let (primary, activeAccounts, _) = activeAccountsAndInfo
var applied: [Signal<Never, NoError>] = []
var activeProductionUserIds = activeAccounts.map({ $0.1 }).filter({ !$0.testingEnvironment }).map({ $0.peerId.id })
var activeTestingUserIds = activeAccounts.map({ $0.1 }).filter({ $0.testingEnvironment }).map({ $0.peerId.id })
let allProductionUserIds = activeProductionUserIds
let allTestingUserIds = activeTestingUserIds
if !settings.allAccounts {
if let primary = primary {
if !primary.testingEnvironment {
activeProductionUserIds = [primary.peerId.id]
activeTestingUserIds = []
} else {
activeProductionUserIds = []
activeTestingUserIds = [primary.peerId.id]
}
} else {
activeProductionUserIds = []
activeTestingUserIds = []
}
}
for (_, account, _) in activeAccounts {
let appliedAps: Signal<Never, NoError>
let appliedVoip: Signal<Never, NoError>
if !activeProductionUserIds.contains(account.peerId.id) && !activeTestingUserIds.contains(account.peerId.id) {
appliedAps = self.apsNotificationToken
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token -> Signal<Never, NoError> in
guard let token = token else {
return .complete()
}
return unregisterNotificationToken(account: account, token: token, type: .aps(encrypt: false), otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id }))
}
appliedVoip = self.voipNotificationToken
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token -> Signal<Never, NoError> in
guard let token = token else {
return .complete()
}
return unregisterNotificationToken(account: account, token: token, type: .voip, otherAccountUserIds: (account.testingEnvironment ? allTestingUserIds : allProductionUserIds).filter({ $0 != account.peerId.id }))
}
} else {
appliedAps = self.apsNotificationToken
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token -> Signal<Never, NoError> in
guard let token = token else {
return .complete()
}
let encrypt: Bool
if #available(iOS 10.0, *) {
encrypt = true
} else {
encrypt = false
}
return registerNotificationToken(account: account, token: token, type: .aps(encrypt: encrypt), sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted)
}
appliedVoip = self.voipNotificationToken
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token -> Signal<Never, NoError> in
guard let token = token else {
return .complete()
}
return registerNotificationToken(account: account, token: token, type: .voip, sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted)
}
}
applied.append(appliedAps)
applied.append(appliedVoip)
}
return combineLatest(applied)
|> ignoreValues
}).start())
}
public func beginNewAuth(testingEnvironment: Bool) {
let _ = self.accountManager.transaction({ transaction -> Void in
let _ = transaction.createAuth([AccountEnvironmentAttribute(environment: testingEnvironment ? .test : .production)])
}).start()
}
public func switchToAccount(id: AccountRecordId, fromSettingsController settingsController: (SettingsController & ViewController)? = nil, withChatListController chatListController: ChatListController? = nil) {
if self.activeAccountsValue?.primary?.id == id {
return
}
assert(Queue.mainQueue().isCurrent())
var chatsBadge: String?
if let rootController = self.mainWindow?.viewController as? TelegramRootController {
if let tabsController = rootController.viewControllers.first as? TabBarController {
for controller in tabsController.controllers {
if let controller = controller as? ChatListController {
chatsBadge = controller.tabBarItem.badgeValue
}
}
if let chatListController = chatListController {
if let index = tabsController.controllers.firstIndex(where: { $0 is ChatListController }) {
var controllers = tabsController.controllers
controllers[index] = chatListController
tabsController.setControllers(controllers, selectedIndex: index)
}
}
}
}
self.switchingData = (settingsController, chatListController, chatsBadge)
let _ = self.accountManager.transaction({ transaction -> Bool in
if transaction.getCurrent()?.0 != id {
transaction.setCurrentId(id)
return true
} else {
return false
}
}).start(next: { value in
if !value {
self.switchingData = (nil, nil, nil)
}
})
}
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
self.navigateToChatImpl(accountId, peerId, messageId)
}
private func updateStatusBarText() {
if case let .inProgress(timestamp) = self.currentCallStatusText {
let text: String
let presentationData = self.currentPresentationData.with { $0 }
if let timestamp = timestamp {
let duration = Int32(CFAbsoluteTimeGetCurrent() - timestamp)
let durationString: String
if duration > 60 * 60 {
durationString = String(format: "%02d:%02d:%02d", arguments: [duration / 3600, (duration / 60) % 60, duration % 60])
} else {
durationString = String(format: "%02d:%02d", arguments: [(duration / 60) % 60, duration % 60])
}
text = presentationData.strings.Call_StatusBar(durationString).0
} else {
text = presentationData.strings.Call_StatusBar("").0
}
self.mainWindow?.setForceInCallStatusBar(text)
} else {
self.mainWindow?.setForceInCallStatusBar(nil)
}
}
public func navigateToCurrentCall() {
if let mainWindow = self.mainWindow, let callController = self.callController {
if callController.isNodeLoaded && callController.view.superview == nil {
mainWindow.hostView.containerView.endEditing(true)
mainWindow.present(callController, on: .calls)
}
}
}
public func accountUserInterfaceInUse(_ id: AccountRecordId) -> Signal<Bool, NoError> {
return Signal { subscriber in
let context: AccountUserInterfaceInUseContext
if let current = self.accountUserInterfaceInUseContexts[id] {
context = current
} else {
context = AccountUserInterfaceInUseContext()
self.accountUserInterfaceInUseContexts[id] = context
}
subscriber.putNext(!context.tokens.isEmpty)
let index = context.subscribers.add({ value in
subscriber.putNext(value)
})
return ActionDisposable { [weak context] in
Queue.mainQueue().async {
if let current = self.accountUserInterfaceInUseContexts[id], current === context {
current.subscribers.remove(index)
if current.isEmpty {
self.accountUserInterfaceInUseContexts.removeValue(forKey: id)
}
}
}
}
}
|> runOn(Queue.mainQueue())
}
public func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable {
assert(Queue.mainQueue().isCurrent())
let context: AccountUserInterfaceInUseContext
if let current = self.accountUserInterfaceInUseContexts[id] {
context = current
} else {
context = AccountUserInterfaceInUseContext()
self.accountUserInterfaceInUseContexts[id] = context
}
let wasEmpty = context.tokens.isEmpty
let index = context.tokens.add(Void())
if wasEmpty {
for f in context.subscribers.copyItems() {
f(true)
}
}
return ActionDisposable { [weak context] in
Queue.mainQueue().async {
if let current = self.accountUserInterfaceInUseContexts[id], current === context {
let wasEmpty = current.tokens.isEmpty
current.tokens.remove(index)
if current.tokens.isEmpty && !wasEmpty {
for f in current.subscribers.copyItems() {
f(false)
}
}
if current.isEmpty {
self.accountUserInterfaceInUseContexts.removeValue(forKey: id)
}
}
}
}
}
public func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) {
handleTextLinkActionImpl(context: context as! AccountContextImpl, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
}
}