diff --git a/Makefile b/Makefile index db4f5cdcf8..ce8502e8ae 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include Utils.makefile -APP_VERSION="7.1.1" +APP_VERSION="7.1.2" CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index e84716e92a..4f386ee9bc 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -229,7 +229,6 @@ static int32_t fixedTimeDifferenceValue = 0; _useTempAuthKeys = useTempAuthKeys; #if DEBUG _tempKeyExpiration = 1 * 60 * 60; - _tempKeyExpiration = 5; #else _tempKeyExpiration = 24 * 60 * 60; #endif diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 569a26d0d6..a91d918a88 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1105,7 +1105,7 @@ func debugRestoreState(basePath:String, name: String) { private let sharedQueue = Queue(name: "org.telegram.postbox.Postbox") -public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { +public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32) -> Signal { let queue = sharedQueue return Signal { subscriber in queue.async { @@ -1177,7 +1177,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, let endTime = CFAbsoluteTimeGetCurrent() print("Postbox load took \((endTime - startTime) * 1000.0) ms") - subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox))) + subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations))) subscriber.putCompletion() break } @@ -1330,7 +1330,7 @@ public final class Postbox { var installedMessageActionsByPeerId: [PeerId: Bag<([StoreMessage], Transaction) -> Void>] = [:] - init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox) { + init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32) { assert(queue.isCurrent()) let startTime = CFAbsoluteTimeGetCurrent() @@ -1534,6 +1534,9 @@ public final class Postbox { for id in self.messageHistoryUnsentTable.get() { transaction.updateMessage(id, update: { message in if !message.flags.contains(.Failed) { + if message.timestamp + 60 * 10 > timestampForAbsoluteTimeBasedOperations { + return .skip + } var flags = StoreMessageFlags(message.flags) flags.remove(.Unsent) flags.remove(.Sending) diff --git a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift index 3de0c39007..6faaa9e537 100644 --- a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift +++ b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSignalKit import Postbox @@ -67,7 +68,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Transaction) -> T) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) return postbox |> mapToSignal { value -> Signal in switch value { diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index 94774cb560..f99d7bb4ea 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -160,7 +160,7 @@ public enum AccountPreferenceEntriesResult { public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys: Set, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) return postbox |> mapToSignal { value -> Signal in switch value { @@ -187,7 +187,7 @@ public enum AccountNoticeEntriesResult { public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) return postbox |> mapToSignal { value -> Signal in switch value { @@ -208,7 +208,7 @@ public enum LegacyAccessChallengeDataResult { public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) return postbox |> mapToSignal { value -> Signal in switch value { @@ -225,7 +225,7 @@ public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecord public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) return postbox |> mapToSignal { result -> Signal in diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index 859e8d3cff..038a86654c 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -209,7 +209,7 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti return manager.allocatedTemporaryAccountId() |> mapToSignal { id -> Signal in let path = "\(rootPath)/\(accountRecordIdPathName(id))" - return openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) + return openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) |> mapToSignal { result -> Signal in switch result { case .upgrading: diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index 9c4e78111e..cf87f6e9d8 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -291,6 +291,7 @@ public final class AccountViewTracker { private var seenLiveLocationDisposables = DisposableDict() private var updatedUnsupportedMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] + private var refreshSecretChatMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] private var nextUpdatedUnsupportedMediaDisposableId: Int32 = 0 private var updatedUnsupportedMediaDisposables = DisposableDict() @@ -631,6 +632,18 @@ public final class AccountViewTracker { |> runOn(self.queue) } + public func updateReplyInfoForMessageId(_ id: MessageId, info: UpdatedMessageReplyInfo) { + self.queue.async { [weak self] in + guard let strongSelf = self else { + return + } + guard let current = strongSelf.updatedViewCountMessageIdsAndTimestamps[id] else { + return + } + strongSelf.updatedViewCountMessageIdsAndTimestamps[id] = ViewCountContextState(timestamp: Int32(CFAbsoluteTimeGetCurrent()), clientId: current.clientId, result: ViewCountContextState.ReplyInfo(commentsPeerId: info.commentsPeerId, maxReadIncomingMessageId: info.maxReadIncomingMessageId, maxMessageId: info.maxMessageId)) + } + } + public func updateViewCountForMessageIds(messageIds: Set, clientId: Int32) { self.queue.async { var addedMessageIds: [MessageId] = [] @@ -1037,6 +1050,98 @@ public final class AccountViewTracker { } } + public func refreshSecretMediaMediaForMessageIds(messageIds: Set) { + self.queue.async { + var addedMessageIds: [MessageId] = [] + let timestamp = Int32(CFAbsoluteTimeGetCurrent()) + for messageId in messageIds { + let messageTimestamp = self.refreshSecretChatMediaMessageIdsAndTimestamps[messageId] + if messageTimestamp == nil { + self.refreshSecretChatMediaMessageIdsAndTimestamps[messageId] = timestamp + addedMessageIds.append(messageId) + } + } + if !addedMessageIds.isEmpty { + for (_, messageIds) in messagesIdsGroupedByPeerId(Set(addedMessageIds)) { + let disposableId = self.nextUpdatedUnsupportedMediaDisposableId + self.nextUpdatedUnsupportedMediaDisposableId += 1 + + if let account = self.account { + let signal = account.postbox.transaction { transaction -> [TelegramMediaFile] in + var result: [TelegramMediaFile] = [] + for id in messageIds { + if let message = transaction.getMessage(id) { + for media in message.media { + if let file = media as? TelegramMediaFile, file.isAnimatedSticker { + result.append(file) + } + } + } + } + return result + } + |> mapToSignal { files -> Signal in + guard !files.isEmpty else { + return .complete() + } + + var stickerPacks = Set() + for file in files { + for attribute in file.attributes { + if case let .Sticker(_, packReferenceValue, _) = attribute, let packReference = packReferenceValue { + if case .id = packReference { + stickerPacks.insert(packReference) + } + } + } + } + + var requests: [Signal] = [] + for reference in stickerPacks { + if case let .id(id, accessHash) = reference { + requests.append(account.network.request(Api.functions.messages.getStickerSet(stickerset: .inputStickerSetID(id: id, accessHash: accessHash))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + }) + } + } + if requests.isEmpty { + return .complete() + } + + return combineLatest(requests) + |> mapToSignal { results -> Signal in + return account.postbox.transaction { transaction -> Void in + for result in results { + switch result { + case let .stickerSet(_, _, documents): + for document in documents { + if let file = telegramMediaFileFromApiDocument(document) { + if transaction.getMedia(file.fileId) != nil { + let _ = transaction.updateMedia(file.fileId, update: file) + } + } + } + default: + break + } + } + } + } + } + |> afterDisposed { [weak self] in + self?.queue.async { + self?.updatedUnsupportedMediaDisposables.set(nil, forKey: disposableId) + } + } + self.updatedUnsupportedMediaDisposables.set(signal.start(), forKey: disposableId) + } + } + } + } + } + public func updateMarkAllMentionsSeen(peerId: PeerId) { self.queue.async { guard let account = self.account else { diff --git a/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift b/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift index af75a65438..f54632ced1 100644 --- a/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift +++ b/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift @@ -130,6 +130,21 @@ private func requestActivity(postbox: Postbox, network: Network, accountPeerId: if let channel = peer as? TelegramChannel, case .broadcast = channel.info { return .complete() } + if let _ = peer as? TelegramUser { + if let presence = transaction.getPeerPresence(peerId: peerId) as? TelegramUserPresence { + switch presence.status { + case .none, .recently, .lastWeek, .lastMonth: + return .complete() + case let .present(statusTimestamp): + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if statusTimestamp < timestamp { + return .complete() + } + } + } else { + return .complete() + } + } if let inputPeer = apiInputPeer(peer) { var flags: Int32 = 0 diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift index d35538ba1f..c9626ed73d 100644 --- a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -145,10 +145,12 @@ private class ReplyThreadHistoryContextImpl { } var channelMessageId: MessageId? + var replyThreadAttribute: ReplyThreadMessageAttribute? for attribute in topMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { channelMessageId = attribute.messageId - break + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + replyThreadAttribute = attribute } } @@ -195,14 +197,41 @@ private class ReplyThreadHistoryContextImpl { } } + let maxReadIncomingMessageId = readInboxMaxId.flatMap { readMaxId in + MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) + } + + if let channelMessageId = channelMessageId, let replyThreadAttribute = replyThreadAttribute { + account.viewTracker.updateReplyInfoForMessageId(channelMessageId, info: AccountViewTracker.UpdatedMessageReplyInfo( + timestamp: Int32(CFAbsoluteTimeGetCurrent()), + commentsPeerId: parsedIndex.id.peerId, + maxReadIncomingMessageId: maxReadIncomingMessageId, + maxMessageId: resolvedMaxMessage + )) + + transaction.updateMessage(channelMessageId, update: { currentMessage in + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReplyThreadMessageAttribute { + attributes[j] = ReplyThreadMessageAttribute( + count: replyThreadAttribute.count, + latestUsers: attribute.latestUsers, + commentsPeerId: attribute.commentsPeerId, + maxMessageId: replyThreadAttribute.maxMessageId, + maxReadMessageId: replyThreadAttribute.maxReadMessageId + ) + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + return .single(DiscussionMessage( messageId: parsedIndex.id, channelMessageId: channelMessageId, isChannelPost: isChannelPost, maxMessage: resolvedMaxMessage, - maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in - MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) - }, + maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c45827dcc1..9ced426c9a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3072,6 +3072,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) + var wasInForeground = true self.applicationInForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground |> distinctUntilChanged |> deliverOn(Queue.mainQueue())).start(next: { [weak self] value in @@ -3081,7 +3082,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.raiseToListen?.applicationResignedActive() strongSelf.stopMediaRecorder() + } else { + if !wasInForeground { + strongSelf.chatDisplayNode.recursivelyEnsureDisplaySynchronously(true) + } } + wasInForeground = value } }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 37ed46c24a..27d97c4b01 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -514,6 +514,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let messageReactionsProcessingManager = ChatMessageThrottledProcessingManager() private let seenLiveLocationProcessingManager = ChatMessageThrottledProcessingManager() private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager() + private let refreshMediaProcessingManager = ChatMessageThrottledProcessingManager() private let messageMentionProcessingManager = ChatMessageThrottledProcessingManager(delay: 0.2) let prefetchManager: InChatPrefetchManager private var currentEarlierPrefetchMessages: [(Message, Media)] = [] @@ -605,6 +606,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.unsupportedMessageProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.updateUnsupportedMediaForMessageIds(messageIds: messageIds) } + self.refreshMediaProcessingManager.process = { [weak context] messageIds in + context?.account.viewTracker.refreshSecretMediaMediaForMessageIds(messageIds: messageIds) + } self.messageMentionProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) } @@ -1174,6 +1178,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var messageIdsWithUpdateableReactions: [MessageId] = [] var messageIdsWithLiveLocation: [MessageId] = [] var messageIdsWithUnsupportedMedia: [MessageId] = [] + var messageIdsWithRefreshMedia: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] var messagesWithPreloadableMediaToEarlier: [(Message, Media)] = [] var messagesWithPreloadableMediaToLater: [(Message, Media)] = [] @@ -1192,6 +1197,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } var contentRequiredValidation = false + var mediaRequiredValidation = false for attribute in message.attributes { if attribute is ViewCountMessageAttribute { if message.id.namespace == Namespaces.Message.Cloud { @@ -1218,6 +1224,24 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } else if let _ = media as? TelegramMediaAction { isAction = true + } else if let telegramFile = media as? TelegramMediaFile { + if telegramFile.isAnimatedSticker, (message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 { + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + if telegramFile.fileId.namespace == Namespaces.Media.CloudFile { + var isValidated = false + attributes: for attribute in telegramFile.attributes { + if case .hintIsValidated = attribute { + isValidated = true + break attributes + } + } + + if !isValidated { + mediaRequiredValidation = true + } + } + } + } } } if !isAction && message.id.peerId.namespace == Namespaces.Peer.CloudChannel { @@ -1226,6 +1250,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if contentRequiredValidation { messageIdsWithUnsupportedMedia.append(message.id) } + if mediaRequiredValidation { + messageIdsWithRefreshMedia.append(message.id) + } if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } @@ -1346,6 +1373,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !messageIdsWithUnsupportedMedia.isEmpty { self.unsupportedMessageProcessingManager.add(messageIdsWithUnsupportedMedia) } + if !messageIdsWithRefreshMedia.isEmpty { + self.refreshMediaProcessingManager.add(messageIdsWithRefreshMedia) + } if !messageIdsWithUnseenPersonalMention.isEmpty { self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0a7ef476ec..5dc98779c2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1066,7 +1066,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview { replyMarkup = attribute } else if let attribute = attribute as? AuthorSignatureMessageAttribute { - if firstMessage.author is TelegramChannel, !attribute.signature.isEmpty { + if let chatPeer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .group = chatPeer.info, firstMessage.author is TelegramChannel, !attribute.signature.isEmpty { authorRank = .custom(attribute.signature) } } diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index c76dbaae28..8522384ec6 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -13,6 +13,7 @@ import PresentationDataUtils import SearchUI import TelegramPermissionsUI import AppBundle +import DeviceAccess public class ComposeController: ViewController { private let context: AccountContext @@ -183,6 +184,42 @@ public class ComposeController: ViewController { } } + self.contactsNode.openCreateContact = { [weak self] in + let _ = (DeviceAccess.authorizationStatus(subject: .contacts) + |> take(1) + |> deliverOnMainQueue).start(next: { status in + guard let strongSelf = self else { + return + } + + switch status { + case .allowed: + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + guard let strongSelf = self else { + return + } + if let peer = peer { + DispatchQueue.main.async { + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id))) + } + } + } else { + (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil), animated: true) + } + }), completed: nil, cancelled: nil)) + case .notDetermined: + DeviceAccess.authorizeAccess(to: .contacts) + default: + let presentationData = strongSelf.presentationData + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + self?.context.sharedContext.applicationBindings.openSettings() + })]), in: .window(.root)) + } + }) + } + self.contactsNode.openCreateNewChannel = { [weak self] in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Sources/ComposeControllerNode.swift b/submodules/TelegramUI/Sources/ComposeControllerNode.swift index 3ed319633c..473daf26c4 100644 --- a/submodules/TelegramUI/Sources/ComposeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ComposeControllerNode.swift @@ -27,6 +27,7 @@ final class ComposeControllerNode: ASDisplayNode { var openCreateNewGroup: (() -> Void)? var openCreateNewSecretChat: (() -> Void)? + var openCreateContact: (() -> Void)? var openCreateNewChannel: (() -> Void)? private var presentationData: PresentationData @@ -39,14 +40,15 @@ final class ComposeControllerNode: ASDisplayNode { var openCreateNewGroupImpl: (() -> Void)? var openCreateNewSecretChatImpl: (() -> Void)? + var openCreateContactImpl: (() -> Void)? var openCreateNewChannelImpl: (() -> Void)? self.contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: [ ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewGroup, icon: .generic(UIImage(bundleImageName: "Contact List/CreateGroupActionIcon")!), action: { openCreateNewGroupImpl?() }), - ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewEncryptedChat, icon: .generic(UIImage(bundleImageName: "Contact List/CreateSecretChatActionIcon")!), action: { - openCreateNewSecretChatImpl?() + ContactListAdditionalOption(title: self.presentationData.strings.NewContact_Title, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: { + openCreateContactImpl?() }), ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: { openCreateNewChannelImpl?() @@ -69,6 +71,10 @@ final class ComposeControllerNode: ASDisplayNode { openCreateNewSecretChatImpl = { [weak self] in self?.openCreateNewSecretChat?() } + openCreateContactImpl = { [weak self] in + self?.contactListNode.listNode.clearHighlightAnimated(true) + self?.openCreateContact?() + } openCreateNewChannelImpl = { [weak self] in self?.openCreateNewChannel?() } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b3f23f2133..06a3944c2c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1250,7 +1250,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr })) } } - if !isPublic && cachedData.linkedDiscussionPeerId == nil { + if !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId { items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistory, action: { interaction.editingOpenPreHistorySetup() })) diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index f1a6fa752c..4966897420 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -627,7 +627,7 @@ public final class OngoingCallContext { let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: tempLogPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in callSessionManager?.sendSignalingData(internalId: internalId, data: data) - }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec) + }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "") strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h index a9e3bebfcd..2e919600d9 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -128,7 +128,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); -- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec; +- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId; - (void)beginTermination; - (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 8de1935b29..a5ce0fc9e2 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -328,7 +328,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } } -- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec { +- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId { self = [super init]; if (self != nil) { _version = version; @@ -427,6 +427,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; dispatch_once(&onceToken, ^{ tgcalls::Register(); }); + + + _tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){ .config = config, .persistentState = (tgcalls::PersistentState){ derivedStateValue }, @@ -435,6 +438,10 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; .rtcServers = parsedRtcServers, .initialNetworkType = callControllerNetworkTypeForType(networkType), .encryptionKey = encryptionKey, + .mediaDevicesConfig = tgcalls::MediaDevicesConfig { + .audioInputId = [audioInputDeviceId UTF8String], + .audioOutputId = [@"" UTF8String] + }, .videoCapture = [_videoCapturer getInterface], .stateUpdated = [weakSelf, queue](tgcalls::State state) { [queue dispatch:^{