diff --git a/Telegram/NotificationService/Serialization.m b/Telegram/NotificationService/Serialization.m index ddfa890a93..f137bbe5df 100644 --- a/Telegram/NotificationService/Serialization.m +++ b/Telegram/NotificationService/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 118; + return 119; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 9d9da7a4e3..ba7e5e2e11 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -249,6 +249,12 @@ public enum ChatSearchDomain: Equatable { } } } + +public enum ChatLocation: Equatable { + case peer(PeerId) + case replyThread(threadMessageId: MessageId, maxReadMessageId: MessageId?) +} + public final class NavigateToChatControllerParams { public let navigationController: NavigationController public let chatController: ChatController? @@ -522,7 +528,7 @@ public protocol SharedAccountContext: class { func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) func openChatMessage(_ params: OpenChatMessageParams) -> Bool - func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> + func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? @@ -632,6 +638,9 @@ public final class TonContext { #endif +public protocol ChatLocationContextHolder: class { +} + public protocol AccountContext: class { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -659,4 +668,7 @@ public protocol AccountContext: class { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? + + func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput + func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) } diff --git a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift index a085442e95..845e350199 100644 --- a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift +++ b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift @@ -29,7 +29,7 @@ public func storedMessageFromSearch(account: Account, message: Message) -> Signa } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } @@ -46,7 +46,7 @@ public func storeMessageFromSearch(transaction: Transaction, message: Message) { } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 6fed3d44c0..88f78da6cc 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode { }, present: { _ in }) let items = (0 ..< 2).map { _ -> ChatListItem in - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] diff --git a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift index 99c05c5600..8a14aebe91 100644 --- a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift +++ b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift @@ -570,7 +570,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc } let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil) - messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) + messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, threadId: nil, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) //Logger.shared.log("loadLegacyMessages", "message \(messageId) completed") diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index acc55b9a41..4c31f5589d 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -250,7 +250,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, geoPlace: media.geoPlace, venue: media.venue, liveBroadcastingTimeout: max(0, timestamp - currentMessage.timestamp - 1)) } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) }) } }).start() diff --git a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift index abf31af054..50f874f213 100644 --- a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift +++ b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift @@ -10,6 +10,7 @@ public enum AdditionalMessageHistoryViewData { case preferencesEntry(ValueBoxKey) case peer(PeerId) case peerIsContact(PeerId) + case message(MessageId) } public enum AdditionalMessageHistoryViewDataEntry { @@ -22,4 +23,5 @@ public enum AdditionalMessageHistoryViewDataEntry { case preferencesEntry(ValueBoxKey, PreferencesEntry?) case peerIsContact(PeerId, Bool) case peer(PeerId, Peer?) + case message(MessageId, Message?) } diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index c535fa6317..a3fc67730f 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -96,7 +96,7 @@ private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer] peers[peerId] = currentPeer } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) } return nil } diff --git a/submodules/Postbox/Sources/ChatLocation.swift b/submodules/Postbox/Sources/ChatLocation.swift index aa10414475..bdf23981a7 100644 --- a/submodules/Postbox/Sources/ChatLocation.swift +++ b/submodules/Postbox/Sources/ChatLocation.swift @@ -1,6 +1,12 @@ import Foundation +import SwiftSignalKit -public enum ChatLocation: Equatable { +public enum ChatLocationInput { case peer(PeerId) - //case group(PeerGroupId) + case external(PeerId, Signal) +} + +enum ResolvedChatLocationInput { + case peer(PeerId) + case external(MessageHistoryViewExternalInput) } diff --git a/submodules/Postbox/Sources/GlobalMessageTagsView.swift b/submodules/Postbox/Sources/GlobalMessageTagsView.swift index c52fa1f315..51bd14f19a 100644 --- a/submodules/Postbox/Sources/GlobalMessageTagsView.swift +++ b/submodules/Postbox/Sources/GlobalMessageTagsView.swift @@ -163,11 +163,11 @@ final class MutableGlobalMessageTagsView: MutablePostboxView { hasChanges = true } case let .intermediateMessage(message): - if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { + if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { hasChanges = true } case let .message(message): - if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { + if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { hasChanges = true } } diff --git a/submodules/Postbox/Sources/IntermediateMessage.swift b/submodules/Postbox/Sources/IntermediateMessage.swift index 6835a546ec..65d29e403c 100644 --- a/submodules/Postbox/Sources/IntermediateMessage.swift +++ b/submodules/Postbox/Sources/IntermediateMessage.swift @@ -34,6 +34,7 @@ class IntermediateMessage { let globallyUniqueId: Int64? let groupingKey: Int64? let groupInfo: MessageGroupInfo? + let threadId: Int64? let timestamp: Int32 let flags: MessageFlags let tags: MessageTags @@ -50,13 +51,14 @@ class IntermediateMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { + init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -71,18 +73,18 @@ class IntermediateMessage { } func withUpdatedTimestamp(_ timestamp: Int32) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupingKey(_ groupingKey: Int64?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedEmbeddedMedia(_ embeddedMedia: ReadBuffer) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) } } diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index ebcce69cd8..6d476aa661 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -107,10 +107,6 @@ public struct MessageIndex: Comparable, Hashable { return MessageIndex(id: MessageId(peerId: self.id.peerId, namespace: self.id.namespace, id: self.id.id == Int32.max ? self.id.id : (self.id.id + 1)), timestamp: self.timestamp) } - public var hashValue: Int { - return self.id.hashValue - } - public static func absoluteUpperBound() -> MessageIndex { return MessageIndex(id: MessageId(peerId: PeerId(namespace: Int32(Int8.max), id: Int32.max), namespace: Int32(Int8.max), id: Int32.max), timestamp: Int32.max) } @@ -492,6 +488,7 @@ public final class Message { public let globallyUniqueId: Int64? public let groupingKey: Int64? public let groupInfo: MessageGroupInfo? + public let threadId: Int64? public let timestamp: Int32 public let flags: MessageFlags public let tags: MessageTags @@ -510,13 +507,14 @@ public final class Message { return MessageIndex(id: self.id, timestamp: self.timestamp) } - public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { + public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -533,23 +531,23 @@ public final class Message { } public func withUpdatedMedia(_ media: [Media]) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedPeers(_ peers: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedFlags(_ flags: MessageFlags) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedAssociatedMessages(_ associatedMessages: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) } } @@ -637,11 +635,22 @@ public enum StoreMessageId { } } +public func makeMessageThreadId(_ messageId: MessageId) -> Int64 { + return (Int64(messageId.namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: messageId.id))) +} + +public func makeThreadIdMessageId(peerId: PeerId, threadId: Int64) -> MessageId { + let namespace = Int32((threadId >> 32) & 0x7fffffff) + let id = Int32(bitPattern: UInt32(threadId & 0xffffffff)) + return MessageId(peerId: peerId, namespace: namespace, id: id) +} + public final class StoreMessage { public let id: StoreMessageId public let timestamp: Int32 public let globallyUniqueId: Int64? public let groupingKey: Int64? + public let threadId: Int64? public let flags: StoreMessageFlags public let tags: MessageTags public let globalTags: GlobalMessageTags @@ -652,10 +661,11 @@ public final class StoreMessage { public let attributes: [MessageAttribute] public let media: [Media] - public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Id(id) self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -668,11 +678,12 @@ public final class StoreMessage { self.media = media } - public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Partial(peerId, namespace) self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -684,11 +695,12 @@ public final class StoreMessage { self.media = media } - public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -712,19 +724,19 @@ public final class StoreMessage { if flags == self.flags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } } public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage { if localTags == self.localTags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) } } } @@ -734,6 +746,7 @@ final class InternalStoreMessage { let timestamp: Int32 let globallyUniqueId: Int64? let groupingKey: Int64? + let threadId: Int64? let flags: StoreMessageFlags let tags: MessageTags let globalTags: GlobalMessageTags @@ -748,11 +761,12 @@ final class InternalStoreMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags diff --git a/submodules/Postbox/Sources/MessageHistoryHolesView.swift b/submodules/Postbox/Sources/MessageHistoryHolesView.swift index 14c65efea0..503580e653 100644 --- a/submodules/Postbox/Sources/MessageHistoryHolesView.swift +++ b/submodules/Postbox/Sources/MessageHistoryHolesView.swift @@ -37,3 +37,40 @@ public final class MessageHistoryHolesView { self.entries = mutableView.entries } } + +public struct MessageHistoryExternalHolesViewEntry: Equatable, Hashable { + public let hole: MessageHistoryViewHole + public let direction: MessageHistoryViewRelativeHoleDirection + public let count: Int + + public init(hole: MessageHistoryViewHole, direction: MessageHistoryViewRelativeHoleDirection, count: Int) { + self.hole = hole + self.direction = direction + self.count = count + } +} + +final class MutableMessageHistoryExternalHolesView { + fileprivate var entries = Set() + + init() { + } + + func update(_ holes: Set) -> Bool { + if self.entries != holes { + self.entries = holes + return true + } else { + return false + } + } +} + +public final class MessageHistoryExternalHolesView { + public let entries: Set + + init(_ mutableView: MutableMessageHistoryExternalHolesView) { + self.entries = mutableView.entries + } +} + diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 4bf428c00a..c4f00ef448 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -50,6 +50,7 @@ private struct MessageDataFlags: OptionSet { static let hasGroupingKey = MessageDataFlags(rawValue: 1 << 2) static let hasGroupInfo = MessageDataFlags(rawValue: 1 << 3) static let hasLocalTags = MessageDataFlags(rawValue: 1 << 4) + static let hasThreadId = MessageDataFlags(rawValue: 1 << 5) } private func extractKey(_ key: ValueBoxKey) -> MessageIndex { @@ -71,6 +72,7 @@ final class MessageHistoryTable: Table { let unsentTable: MessageHistoryUnsentTable let failedTable: MessageHistoryFailedTable let tagsTable: MessageHistoryTagsTable + let threadsTable: MessageHistoryThreadsTable let globalTagsTable: GlobalMessageHistoryTagsTable let localTagsTable: LocalMessageHistoryTagsTable let readStateTable: MessageHistoryReadStateTable @@ -79,7 +81,7 @@ final class MessageHistoryTable: Table { let summaryTable: MessageHistoryTagsSummaryTable let pendingActionsTable: PendingMessageActionsTable - init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { + init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { self.seedConfiguration = seedConfiguration self.messageHistoryIndexTable = messageHistoryIndexTable self.messageHistoryHoleIndexTable = messageHistoryHoleIndexTable @@ -89,6 +91,7 @@ final class MessageHistoryTable: Table { self.unsentTable = unsentTable self.failedTable = failedTable self.tagsTable = tagsTable + self.threadsTable = threadsTable self.globalTagsTable = globalTagsTable self.localTagsTable = localTagsTable self.readStateTable = readStateTable @@ -268,6 +271,9 @@ final class MessageHistoryTable: Table { } } } + if let threadId = message.threadId { + self.threadsTable.add(threadId: threadId, index: message.index) + } let globalTags = message.globalTags.rawValue if globalTags != 0 { for i in 0 ..< 32 { @@ -374,10 +380,10 @@ final class MessageHistoryTable: Table { for message in messages { switch message.id { case let .Id(id): - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) case let .Partial(peerId, namespace): let id = self.historyMetadataTable.getNextMessageIdAndIncrement(peerId, namespace: namespace) - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) } } return internalStoreMessages @@ -979,6 +985,9 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1001,6 +1010,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } if self.seedConfiguration.peerNamespacesRequiringMessageTextIndex.contains(message.id.peerId.namespace) { var indexableText = message.text @@ -1160,7 +1172,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) return result } @@ -1237,6 +1249,9 @@ final class MessageHistoryTable: Table { for tag in message.tags { self.tagsTable.remove(tags: tag, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } + if let threadId = message.threadId { + self.threadsTable.remove(threadId: threadId, index: index) + } for tag in message.globalTags { self.globalTagsTable.remove(tag, index: index) } @@ -1331,7 +1346,7 @@ final class MessageHistoryTable: Table { } withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) }) let operation: MessageHistoryOperation = .UpdateEmbeddedMedia(index, updatedEmbeddedMediaBuffer.makeReadBufferAndReset()) @@ -1419,6 +1434,14 @@ final class MessageHistoryTable: Table { self.tagsTable.add(tags: message.tags, index: message.index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } } + if previousMessage.threadId != message.threadId || index != message.index { + if let threadId = previousMessage.threadId { + self.threadsTable.remove(threadId: threadId, index: index) + } + if let threadId = message.threadId { + self.threadsTable.add(threadId: threadId, index: message.index) + } + } if !previousMessage.globalTags.isEmpty || !message.globalTags.isEmpty { if !previousMessage.globalTags.isEmpty { @@ -1542,6 +1565,9 @@ final class MessageHistoryTable: Table { if !updatedLocalTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1564,6 +1590,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = updatedLocalTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } var flags = MessageFlags(message.flags) sharedBuffer.write(&flags.rawValue, offset: 0, length: 4) @@ -1708,7 +1737,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) for media in mediaToUpdate { if let id = media.id { @@ -1786,7 +1815,7 @@ final class MessageHistoryTable: Table { let updatedIndex = MessageIndex(id: index.id, timestamp: timestamp) - let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) + let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, threadId: previousMessage.threadId, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) return (previousMessage.tags, previousMessage.globalTags) } else { return nil @@ -1826,7 +1855,7 @@ final class MessageHistoryTable: Table { var updatedReferencedMedia = message.referencedMedia updatedReferencedMedia.append(extractedMedia.id!) withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) }) return extractedMedia @@ -1863,6 +1892,9 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1885,6 +1917,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } var flagsValue: UInt32 = message.flags.rawValue sharedBuffer.write(&flagsValue, offset: 0, length: 4) @@ -2087,6 +2122,13 @@ final class MessageHistoryTable: Table { localTags = LocalMessageTags(rawValue: localTagsValue) } + var threadId: Int64? + if dataFlags.contains(.hasThreadId) { + var threadIdValue: Int64 = 0 + value.read(&threadIdValue, offset: 0, length: 8) + threadId = threadIdValue + } + var flagsValue: UInt32 = 0 value.read(&flagsValue, offset: 0, length: 4) let flags = MessageFlags(rawValue: flagsValue) @@ -2193,7 +2235,7 @@ final class MessageHistoryTable: Table { referencedMediaIds.append(MediaId(namespace: idNamespace, id: idId)) } - return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) + return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, threadId: threadId, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) } else { preconditionFailure() } @@ -2342,7 +2384,7 @@ final class MessageHistoryTable: Table { } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) } func renderMessagePeers(_ message: Message, peerTable: PeerTable) -> Message { @@ -2677,11 +2719,74 @@ final class MessageHistoryTable: Table { return (result, mediaRefs, count == 0 ? nil : lastIndex) } - func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { + func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, threadId: Int64?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { precondition(fromIndex.id.peerId == toIndex.id.peerId) precondition(fromIndex.id.namespace == toIndex.id.namespace) var result: [IntermediateMessage] = [] - if let tag = tag { + if let threadId = threadId { + let indices: [MessageIndex] + if fromIndex < toIndex { + indices = self.threadsTable.laterIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) + } else { + indices = self.threadsTable.earlierIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) + } + for index in indices { + if fromIndex < toIndex { + if index < fromIndex || index > toIndex { + continue + } + } else { + if index < toIndex || index > fromIndex { + continue + } + } + if let message = self.getMessage(index) { + result.append(message) + } else { + assertionFailure() + } + } + + // Unoptimized + /*var startKey: ValueBoxKey + if includeFrom && fromIndex != MessageIndex.upperBound(peerId: peerId, namespace: namespace) { + if fromIndex < toIndex { + startKey = self.key(fromIndex).predecessor + } else { + startKey = self.key(fromIndex).successor + } + } else { + startKey = self.key(fromIndex) + } + while true { + var found = false + var lastKey = startKey + self.valueBox.range(self.table, start: startKey, end: self.key(toIndex), values: { key, value in + lastKey = key + found = true + + let message = self.readIntermediateEntry(key, value: value).message + assert(message.id.peerId == peerId && message.id.namespace == namespace) + assert(message.index == extractKey(key)) + if message.threadId == threadId { + result.append(message) + + if result.count >= limit { + return false + } + } + return true + }, limit: max(64, limit - result.count)) + + if !found { + break + } + if result.count >= limit { + break + } + startKey = lastKey + }*/ + } else if let tag = tag { let indices: [MessageIndex] if fromIndex < toIndex { indices = self.tagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift new file mode 100644 index 0000000000..776893cdcd --- /dev/null +++ b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift @@ -0,0 +1,98 @@ +import Foundation + +private func extractKey(_ key: ValueBoxKey) -> MessageIndex { + return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) +} + +class MessageHistoryThreadsTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let sharedKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4) + + override init(valueBox: ValueBox, table: ValueBoxTable) { + super.init(valueBox: valueBox, table: table) + } + + private func key(threadId: Int64, index: MessageIndex, key: ValueBoxKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4)) -> ValueBoxKey { + key.setInt64(0, value: index.id.peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setInt32(8 + 8, value: index.id.namespace) + key.setInt32(8 + 8 + 4, value: index.timestamp) + key.setInt32(8 + 8 + 4 + 4, value: index.id.id) + return key + } + + private func lowerBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + let key = ValueBoxKey(length: 8 + 8 + 4) + key.setInt64(0, value: peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setInt32(8 + 8, value: namespace) + return key + } + + private func upperBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + return self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace).successor + } + + func add(threadId: Int64, index: MessageIndex) { + self.valueBox.set(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), value: MemoryBuffer()) + } + + func remove(threadId: Int64, index: MessageIndex) { + self.valueBox.remove(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), secure: false) + } + + func earlierIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(threadId: threadId, index: index).successor + } else { + key = self.key(threadId: threadId, index: index) + } + } else { + key = self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(extractKey(key)) + return true + }, limit: count) + return indices + } + + func laterIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(threadId: threadId, index: index).predecessor + } else { + key = self.key(threadId: threadId, index: index) + } + } else { + key = self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(extractKey(key)) + return true + }, limit: count) + return indices + } + + func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + precondition(lowerBound.id.namespace == namespace) + precondition(upperBound.id.namespace == namespace) + var lowerBoundKey = self.key(threadId: threadId, index: lowerBound) + if lowerBound.timestamp > 1 { + lowerBoundKey = lowerBoundKey.predecessor + } + var upperBoundKey = self.key(threadId: threadId, index: upperBound) + if upperBound.timestamp < Int32.max - 1 { + upperBoundKey = upperBoundKey.successor + } + return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) + } +} diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 87c2e0db6a..34d6af6ceb 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -3,9 +3,16 @@ import Foundation public struct MessageHistoryViewPeerHole: Equatable, Hashable, CustomStringConvertible { public let peerId: PeerId public let namespace: MessageId.Namespace + public let threadId: Int64? + + public init(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64?) { + self.peerId = peerId + self.namespace = namespace + self.threadId = threadId + } public var description: String { - return "peerId: \(self.peerId), namespace: \(self.namespace)" + return "peerId: \(self.peerId), namespace: \(self.namespace), threadId: \(String(describing: self.threadId))" } } @@ -126,18 +133,18 @@ enum MutableMessageHistoryEntry { func updatedTimestamp(_ timestamp: Int32) -> MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, location, monthLocation): - let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) + let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) return .IntermediateMessageEntry(updatedMessage, location, monthLocation) case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers): let message = value.message - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } } func getAssociatedMessageIds() -> [MessageId] { switch self { - case let .IntermediateMessageEntry(message, location, monthLocation): + case .IntermediateMessageEntry: return [] case let .MessageEntry(value, _, _): return value.message.associatedMessageIds @@ -236,9 +243,45 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } -public enum MessageHistoryViewPeerIds: Equatable { +public final class MessageHistoryViewExternalInput: Equatable { + public let peerId: PeerId + public let threadId: Int64 + public let maxReadMessageId: MessageId? + public let holes: [MessageId.Namespace: IndexSet] + + public init( + peerId: PeerId, + threadId: Int64, + maxReadMessageId: MessageId?, + holes: [MessageId.Namespace: IndexSet] + ) { + self.peerId = peerId + self.threadId = threadId + self.maxReadMessageId = maxReadMessageId + self.holes = holes + } + + public static func ==(lhs: MessageHistoryViewExternalInput, rhs: MessageHistoryViewExternalInput) -> Bool { + if lhs === rhs { + return true + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.threadId != rhs.threadId { + return false + } + if lhs.holes != rhs.holes { + return false + } + return true + } +} + +public enum MessageHistoryViewInput: Equatable { case single(PeerId) case associated(PeerId, MessageId?) + case external(MessageHistoryViewExternalInput) } public enum MessageHistoryViewReadState { @@ -254,7 +297,7 @@ public enum HistoryViewInputAnchor: Equatable { } final class MutableMessageHistoryView { - private(set) var peerIds: MessageHistoryViewPeerIds + private(set) var peerIds: MessageHistoryViewInput let tag: MessageTags? let namespaces: MessageIdNamespaces private let orderStatistics: MessageHistoryViewOrderStatistics @@ -274,7 +317,7 @@ final class MutableMessageHistoryView { fileprivate var isAddedToChatList: Bool - init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { + init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewInput, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { self.anchor = inputAnchor self.orderStatistics = orderStatistics @@ -288,14 +331,14 @@ final class MutableMessageHistoryView { self.topTaggedMessages = topTaggedMessages self.additionalDatas = additionalDatas - let mainPeerId: PeerId switch peerIds { case let .associated(peerId, _): - mainPeerId = peerId + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil case let .single(peerId): - mainPeerId = peerId + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil + case let .external(input): + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: input.peerId) != nil } - self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds) if case let .loading(loadingState) = self.state { @@ -355,12 +398,14 @@ final class MutableMessageHistoryView { self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId) } } + case .external: + break } } func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { var operations: [[MessageHistoryOperation]] = [] - var peerIdsSet = Set() + var holePeerIdsSet = Set() if !transaction.chatListOperations.isEmpty { let mainPeerId: PeerId @@ -369,52 +414,67 @@ final class MutableMessageHistoryView { mainPeerId = peerId case let .single(peerId): mainPeerId = peerId + case let .external(input): + mainPeerId = input.peerId } self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil } switch self.peerIds { case let .single(peerId): - peerIdsSet.insert(peerId) + holePeerIdsSet.insert(peerId) if let value = transaction.currentOperationsByPeerId[peerId] { operations.append(value) } case .associated: switch self.peerIds { - case .single: + case .single, .external: assertionFailure() case let .associated(mainPeerId, associatedPeerId): - peerIdsSet.insert(mainPeerId) + holePeerIdsSet.insert(mainPeerId) if let associatedPeerId = associatedPeerId { - peerIdsSet.insert(associatedPeerId.peerId) + holePeerIdsSet.insert(associatedPeerId.peerId) } } for (peerId, value) in transaction.currentOperationsByPeerId { - if peerIdsSet.contains(peerId) { + if holePeerIdsSet.contains(peerId) { operations.append(value) } } + case let .external(input): + if let value = transaction.currentOperationsByPeerId[input.peerId] { + operations.append(value) + } } var hasChanges = false let unwrappedTag: MessageTags = self.tag ?? [] + let threadId: Int64? + switch self.peerIds { + case .single, .associated: + threadId = nil + case let .external(input): + threadId = input.threadId + } switch self.state { case let .loading(loadingState): for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true + if threadId == nil { + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true + } } } if matchesSpace { - if peerIdsSet.contains(key.peerId) { + if holePeerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -447,13 +507,22 @@ final class MutableMessageHistoryView { for operation in operationSet { switch operation { case let .InsertMessage(message): + var matches = false if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) { - if self.namespaces.contains(message.id.namespace) { - if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { - hasChanges = true + if threadId == nil || message.threadId == threadId { + if self.namespaces.contains(message.id.namespace) { + matches = true + if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { + hasChanges = true + } } } } + if !matches { + if loadedState.addAssociated(entry: .IntermediateMessageEntry(message, nil, nil)) { + hasChanges = true + } + } case let .Remove(indicesAndTags): for (index, _) in indicesAndTags { if self.namespaces.contains(index.id.namespace) { @@ -491,16 +560,18 @@ final class MutableMessageHistoryView { } for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true + if threadId == nil { + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true + } } } if matchesSpace { - if peerIdsSet.contains(key.peerId) { + if holePeerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -579,6 +650,50 @@ final class MutableMessageHistoryView { } case .cachedPeerDataMessages: break + case let .message(id, _): + if let operations = transaction.currentOperationsByPeerId[id.peerId] { + var updateMessage = false + findOperation: for operation in operations { + switch operation { + case let .InsertMessage(message): + if message.id == id { + updateMessage = true + break findOperation + } + case let .Remove(indices): + for (index, _) in indices { + if index.id == id { + updateMessage = true + break findOperation + } + } + case let .UpdateEmbeddedMedia(index, _): + if index.id == id { + updateMessage = true + break findOperation + } + case let .UpdateGroupInfos(dict): + if dict[id] != nil { + updateMessage = true + break findOperation + } + case let .UpdateTimestamp(index, _): + if index.id == id { + updateMessage = true + break findOperation + } + case .UpdateReadState: + break + } + } + if updateMessage { + let message = postbox.messageHistoryIndexTable.getIndex(id).flatMap(postbox.messageHistoryTable.getMessage).flatMap { message in + postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable) + } + self.additionalDatas[i] = .message(id, message) + hasChanges = true + } + } case let .peerChatState(peerId, _): if transaction.currentUpdatedPeerChatStates.contains(peerId) { self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState) @@ -667,19 +782,21 @@ final class MutableMessageHistoryView { } if !transaction.currentPeerHoleOperations.isEmpty { - var peerIdsSet: [PeerId] = [] - switch peerIds { + var holePeerIdsSet: [PeerId] = [] + switch self.peerIds { case let .single(peerId): - peerIdsSet.append(peerId) + holePeerIdsSet.append(peerId) case let .associated(peerId, associatedId): - peerIdsSet.append(peerId) + holePeerIdsSet.append(peerId) if let associatedId = associatedId { - peerIdsSet.append(associatedId.peerId) + holePeerIdsSet.append(associatedId.peerId) } + case .external: + break } let space: MessageHistoryHoleSpace = self.tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere for key in transaction.currentPeerHoleOperations.keys { - if peerIdsSet.contains(key.peerId) && key.space == space { + if holePeerIdsSet.contains(key.peerId) && key.space == space { hasChanges = true } } @@ -707,8 +824,8 @@ final class MutableMessageHistoryView { switch loadingSample { case .ready: return nil - case let .loadHole(peerId, namespace, _, id): - return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) + case let .loadHole(peerId, namespace, _, threadId, id): + return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace, threadId: threadId)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) } case let .loaded(loadedSample): if let hole = loadedSample.hole { @@ -718,7 +835,7 @@ final class MutableMessageHistoryView { } else { direction = .aroundId(MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId)) } - return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace)), direction, self.fillCount * 2) + return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace, threadId: hole.threadId)), direction, self.fillCount * 2) } else { return nil } @@ -838,66 +955,122 @@ public final class MessageHistoryView { self.fixedReadStates = mutableView.combinedReadStates - if let combinedReadStates = mutableView.combinedReadStates { - switch combinedReadStates { - case let .peer(states): - var hasUnread = false - for (_, readState) in states { - if readState.count > 0 { - hasUnread = true - break + switch mutableView.peerIds { + case .single, .associated: + if let combinedReadStates = mutableView.combinedReadStates { + switch combinedReadStates { + case let .peer(states): + var hasUnread = false + for (_, readState) in states { + if readState.count > 0 { + hasUnread = true + break + } } + + var maxIndex: MessageIndex? + + if hasUnread { + var peerIds = Set() + for entry in entries { + peerIds.insert(entry.index.id.peerId) + } + for peerId in peerIds { + if let combinedReadState = states[peerId] { + for (namespace, state) in combinedReadState.states { + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { + maxNamespaceIndex = entry.index + break + } + index -= 1 + } + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.predecessor() + break + } + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break + } + } + } + if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex + } + } + } + } + } + self.maxReadIndex = maxIndex } - + } else { + self.maxReadIndex = nil + } + case let .external(input): + if let maxReadMesageId = input.maxReadMessageId { var maxIndex: MessageIndex? + let hasUnread = true if hasUnread { var peerIds = Set() for entry in entries { peerIds.insert(entry.index.id.peerId) } for peerId in peerIds { - if let combinedReadState = states[peerId] { - for (namespace, state) in combinedReadState.states { - var maxNamespaceIndex: MessageIndex? - var index = entries.count - 1 - for entry in entries.reversed() { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { - maxNamespaceIndex = entry.index - break - } - index -= 1 + if peerId != maxReadMesageId.peerId { + continue + } + let namespace = maxReadMesageId.namespace + + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && entry.index.id <= maxReadMesageId { + maxNamespaceIndex = entry.index + break + } + index -= 1 + } + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.predecessor() + break } - if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { - index = 0 - for entry in entries { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { - maxNamespaceIndex = entry.index.predecessor() - break - } - index += 1 - } - } - if let _ = maxNamespaceIndex , index + 1 < entries.count { - for i in index + 1 ..< entries.count { - if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { - maxNamespaceIndex = entries[i].message.index - } else { - break - } - } - } - if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { - maxIndex = maxNamespaceIndex + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break } } } + if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex + } } } self.maxReadIndex = maxIndex + } else { + self.maxReadIndex = nil } - } else { - self.maxReadIndex = nil } self.entries = entries diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 7f46f97daa..1ffb84eae4 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1,5 +1,43 @@ import Foundation +public enum MessageHistoryInput: Equatable, Hashable { + case automatic(MessageTags?) + case external(MessageHistoryViewExternalInput) + + public func hash(into hasher: inout Hasher) { + switch self { + case .automatic: + hasher.combine(1) + case .external: + hasher.combine(2) + } + } +} + +private extension MessageHistoryInput { + func fetch(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { + switch self { + case let .automatic(tag): + return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: nil, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) + case let .external(input): + return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: input.threadId, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) + } + } + + func getMessageCountInRange(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + switch self { + case let .automatic(tag): + if let tag = tag { + return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } else { + return 0 + } + case .external: + return 0 + } + } +} + public struct PeerIdAndNamespace: Hashable { public let peerId: PeerId public let namespace: MessageId.Namespace @@ -10,11 +48,16 @@ public struct PeerIdAndNamespace: Hashable { } } -private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, seedConfiguration: SeedConfiguration) -> Bool { - guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { - return false +private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, input: MessageHistoryInput, seedConfiguration: SeedConfiguration) -> Bool { + switch input { + case .automatic: + guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { + return false + } + return messageNamespaces[peerIdAndNamespace.namespace] != nil + case .external: + return true } - return messageNamespaces[peerIdAndNamespace.namespace] != nil } private struct MessageMonthIndex: Equatable { @@ -246,6 +289,7 @@ struct SampledHistoryViewHole: Equatable { let peerId: PeerId let namespace: MessageId.Namespace let tag: MessageTags? + let threadId: Int64? let indices: IndexSet let startId: MessageId.Id let endId: MessageId.Id? @@ -291,22 +335,31 @@ private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, th } } -private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { +private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { var clipRanges: [ClosedRange] = [] var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)? + var tag: MessageTags? + var threadId: Int64? + switch input { + case let .automatic(value): + tag = value + case let .external(value): + threadId = value.threadId + } + for (space, indices) in holes.holesBySpace { if indices.isEmpty { continue } - assert(canContainHoles(space, seedConfiguration: seedConfiguration)) + assert(canContainHoles(space, input: input, seedConfiguration: seedConfiguration)) switch anchor { case .lowerBound, .upperBound: break case let .index(index): if index.id.peerId == space.peerId && index.id.namespace == space.namespace { if indices.contains(Int(index.id.id)) { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: index.id.id, endId: nil)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: index.id.id, endId: nil)) } } } @@ -319,9 +372,9 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere holeBounds = (Int32.max - 1, 1) } if case let .index(index) = anchor, index.id.peerId == space.peerId { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) } else { - sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) continue } } @@ -358,7 +411,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere } else { holeStartIndex = indices[indices.endIndex] } - lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: 1)) + lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: 1)) if i == -1 { if items.lowerOrAtAnchor.count == 0 { @@ -426,7 +479,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere } else { holeStartIndex = indices[indices.startIndex] } - higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) + higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) if i == items.higherThanAnchor.count { if items.higherThanAnchor.count == 0 { @@ -772,8 +825,8 @@ struct HistoryViewLoadedSample { final class HistoryViewLoadedState { let anchor: HistoryViewAnchor - let tag: MessageTags? let namespaces: MessageIdNamespaces + let input: MessageHistoryInput let statistics: MessageHistoryViewOrderStatistics let halfLimit: Int let seedConfiguration: SeedConfiguration @@ -781,10 +834,9 @@ final class HistoryViewLoadedState { var holes: HistoryViewHoles var spacesWithRemovals = Set() - init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) { + init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput, postbox: Postbox, holes: HistoryViewHoles) { precondition(halfLimit >= 3) self.anchor = anchor - self.tag = tag self.namespaces = namespaces self.statistics = statistics self.halfLimit = halfLimit @@ -793,15 +845,22 @@ final class HistoryViewLoadedState { self.holes = holes var peerIds: [PeerId] = [] + let input: MessageHistoryInput switch locations { case let .single(peerId): peerIds.append(peerId) + input = .automatic(tag) case let .associated(peerId, associatedId): peerIds.append(peerId) if let associatedId = associatedId { peerIds.append(associatedId.peerId) } + input = .automatic(tag) + case let .external(external): + peerIds.append(external.peerId) + input = .external(external) } + self.input = input var spaces: [PeerIdAndNamespace] = [] for peerId in peerIds { @@ -849,7 +908,7 @@ final class HistoryViewLoadedState { } else { nextLowerIndex = (anchorIndex, true) } - lowerOrAtAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) + lowerOrAtAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) } if higherThanAnchorMessages.count < self.halfLimit { let nextHigherIndex: MessageIndex @@ -858,7 +917,7 @@ final class HistoryViewLoadedState { } else { nextHigherIndex = anchorIndex } - higherThanAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) + higherThanAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) } lowerOrAtAnchorMessages.reverse() @@ -868,10 +927,10 @@ final class HistoryViewLoadedState { var entries = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages) - if let tag = self.tag, self.statistics.contains(.combinedLocation), let first = entries.first { + if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.combinedLocation), let first = entries.first { let messageIndex = first.index - let previousCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) - let nextCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) + let previousCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) + let nextCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) let initialLocation = MessageHistoryEntryLocation(index: previousCount - 1, count: previousCount + nextCount - 1) var nextLocation = initialLocation @@ -887,10 +946,10 @@ final class HistoryViewLoadedState { } } - if let tag = self.tag, self.statistics.contains(.locationWithinMonth), let first = entries.first { + if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.locationWithinMonth), let first = entries.first { let messageIndex = first.index let monthIndex = MessageMonthIndex(timestamp: messageIndex.timestamp) - let count = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) + let count = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) var nextLocation: (MessageMonthIndex, Int) = (monthIndex, count - 1) @@ -911,19 +970,19 @@ final class HistoryViewLoadedState { } } - if canContainHoles(space, seedConfiguration: self.seedConfiguration) { + if canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration) { entries.fixMonotony() } self.orderedEntriesBySpace[space] = entries } func insertHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) return self.holes.insertHole(space: space, range: range) } func removeHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) return self.holes.removeHole(space: space, range: range) } @@ -1025,7 +1084,7 @@ final class HistoryViewLoadedState { messageMedia.append(media) } } - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } case .IntermediateMessageEntry: @@ -1104,6 +1163,28 @@ final class HistoryViewLoadedState { } } + func addAssociated(entry: MutableMessageHistoryEntry) -> Bool { + var updated = false + + for (space, _) in self.orderedEntriesBySpace { + if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(entry.index.id) { + for associatedIndex in associatedIndices { + let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in + switch current { + case .IntermediateMessageEntry: + return current + case let .MessageEntry(messageEntry, _, reloadPeers): + updated = true + return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers) + } + }) + } + } + } + + return updated + } + func remove(index: MessageIndex) -> Bool { let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace) if self.orderedEntriesBySpace[space] == nil { @@ -1114,7 +1195,7 @@ final class HistoryViewLoadedState { if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(index.id) { for associatedIndex in associatedIndices { - self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in + let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in switch current { case .IntermediateMessageEntry: return current @@ -1147,7 +1228,7 @@ final class HistoryViewLoadedState { self.spacesWithRemovals.removeAll() } let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit) - let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) + let (clipRanges, sampledHole) = sampleHoleRanges(input: self.input, orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) var holesToLower = false var holesToHigher = false @@ -1229,8 +1310,7 @@ final class HistoryViewLoadedState { } } -private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { - var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] +private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { var peerIds: [PeerId] = [] switch locations { case let .single(peerId): @@ -1240,37 +1320,59 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, if let associatedId = associatedId { peerIds.append(associatedId.peerId) } + case let .external(input): + peerIds.append(input.peerId) } - let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere - for peerId in peerIds { - for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { - if namespaces.contains(namespace) { - let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) - if !indices.isEmpty { - let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) - assert(canContainHoles(peerIdAndNamespace, seedConfiguration: postbox.seedConfiguration)) - holesBySpace[peerIdAndNamespace] = indices + switch locations { + case .single, .associated: + var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] + let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere + for peerId in peerIds { + for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { + if namespaces.contains(namespace) { + let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .automatic(tag), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } } } } + return holesBySpace + case let .external(input): + var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] + for peerId in peerIds { + for (namespace, indices) in input.holes { + if namespaces.contains(namespace) { + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .external(input), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } + } + } + } + return holesBySpace } - return holesBySpace } enum HistoryViewLoadingSample { case ready(HistoryViewAnchor, HistoryViewHoles) - case loadHole(PeerId, MessageId.Namespace, MessageTags?, MessageId.Id) + case loadHole(PeerId, MessageId.Namespace, MessageTags?, Int64?, MessageId.Id) } final class HistoryViewLoadingState { var messageId: MessageId let tag: MessageTags? + let threadId: Int64? let halfLimit: Int var holes: HistoryViewHoles - init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { + init(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, threadId: Int64?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { self.messageId = messageId self.tag = tag + self.threadId = threadId self.halfLimit = halfLimit self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)) } @@ -1287,7 +1389,7 @@ final class HistoryViewLoadingState { while true { if let indices = self.holes.holesBySpace[PeerIdAndNamespace(peerId: self.messageId.peerId, namespace: self.messageId.namespace)] { if indices.contains(Int(messageId.id)) { - return .loadHole(messageId.peerId, messageId.namespace, self.tag, messageId.id) + return .loadHole(messageId.peerId, messageId.namespace, self.tag, self.threadId, messageId.id) } } @@ -1312,7 +1414,7 @@ enum HistoryViewState { case loaded(HistoryViewLoadedState) case loading(HistoryViewLoadingState) - init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) { + init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput) { switch inputAnchor { case let .index(index): self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) @@ -1327,6 +1429,9 @@ enum HistoryViewState { anchorPeerId = peerId case let .associated(peerId, _): anchorPeerId = peerId + case .external: + self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) + return } if postbox.chatListIndexTable.get(peerId: anchorPeerId).includedIndex(peerId: anchorPeerId) != nil, let combinedState = postbox.readStateTable.getCombinedState(anchorPeerId) { var messageId: MessageId? @@ -1352,7 +1457,7 @@ enum HistoryViewState { } } if let messageId = messageId { - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: nil, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): @@ -1367,7 +1472,14 @@ enum HistoryViewState { preconditionFailure() } case let .message(messageId): - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + var threadId: Int64? + switch locations { + case let .external(input): + threadId = input.threadId + default: + break + } + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: threadId, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index caa7f03362..79ebc808dc 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -33,7 +33,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { private let count: Int private var anchor: HistoryViewInputAnchor private var wrappedView: MutableMessageHistoryView - private var peerIds: MessageHistoryViewPeerIds + private var peerIds: MessageHistoryViewInput fileprivate var closestHole: MessageOfInterestHole? fileprivate var closestLaterMedia: [HolesViewMedia] = [] @@ -43,11 +43,11 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { self.count = count let mainPeerId: PeerId - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): mainPeerId = id - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.peerIds = peerIds var anchor: HistoryViewInputAnchor = .upperBound @@ -127,10 +127,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if self.anchor != anchor { self.anchor = anchor - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) return self.updateFromView() @@ -146,6 +146,9 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if let attachedMessageId = attachedMessageId { allPeerIds.append(attachedMessageId.peerId) } + case .external: + allPeerIds = [] + break } for (key, _) in transaction.currentPeerHoleOperations { if allPeerIds.contains(key.peerId) { @@ -155,10 +158,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { } } if reloadView { - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 4dcf6d4dcc..3394d6b395 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -38,6 +38,14 @@ public final class Transaction { } } + public func messageExists(id: MessageId) -> Bool { + if let postbox = self.postbox { + return postbox.messageHistoryIndexTable.exists(id) + } else { + return false + } + } + public func countIncomingMessage(id: MessageId) { assert(!self.disposed) if let postbox = self.postbox { @@ -1220,6 +1228,7 @@ public final class Postbox { let messageHistoryUnsentTable: MessageHistoryUnsentTable let messageHistoryFailedTable: MessageHistoryFailedTable let messageHistoryTagsTable: MessageHistoryTagsTable + let messageHistoryThreadsTable: MessageHistoryThreadsTable let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable let peerChatStateTable: PeerChatStateTable @@ -1302,6 +1311,7 @@ public final class Postbox { self.pendingMessageActionsMetadataTable = PendingMessageActionsMetadataTable(valueBox: self.valueBox, table: PendingMessageActionsMetadataTable.tableSpec(45)) self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), metadataTable: self.pendingMessageActionsMetadataTable) self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) + self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62)) self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39)) self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52)) self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) @@ -1313,7 +1323,7 @@ public final class Postbox { self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), indexTable: self.timestampBasedMessageAttributesIndexTable) self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41)) self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55)) - self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) + self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13)) self.peerNameTokenIndexTable = ReverseIndexReferenceTable(valueBox: self.valueBox, table: ReverseIndexReferenceTable.tableSpec(26)) self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable) @@ -1355,6 +1365,7 @@ public final class Postbox { tables.append(self.messageHistoryUnsentTable) tables.append(self.messageHistoryFailedTable) tables.append(self.messageHistoryTagsTable) + tables.append(self.messageHistoryThreadsTable) tables.append(self.globalMessageHistoryTagsTable) tables.append(self.localMessageHistoryTagsTable) tables.append(self.messageHistoryIndexTable) @@ -1467,7 +1478,7 @@ public final class Postbox { if let forwardInfo = message.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) } else { return .skip } @@ -1666,7 +1677,7 @@ public final class Postbox { } fileprivate func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] { - let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId), tagMask: nil) + let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId)) switch peerIds { case let .associated(_, messageId): if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { @@ -1682,7 +1693,7 @@ public final class Postbox { if let states = initialCombinedStates?.states { for (namespace, state) in states { if namespace != messageIndex.id.namespace && state.count != 0 { - if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { + if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, threadId: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { resultIds.append(contentsOf: self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: item.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)) } } @@ -2333,93 +2344,120 @@ public final class Postbox { } } - func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewPeerIds { - var peerIds: MessageHistoryViewPeerIds + func resolvedChatLocationInput(chatLocation: ChatLocationInput) -> Signal { + switch chatLocation { + case let .peer(peerId): + return .single(.peer(peerId)) + case let .external(_, input): + return input + |> map { value -> ResolvedChatLocationInput in + return .external(value) + } + } + } + + func peerIdsForLocation(_ chatLocation: ResolvedChatLocationInput) -> MessageHistoryViewInput { + var peerIds: MessageHistoryViewInput switch chatLocation { case let .peer(peerId): peerIds = .single(peerId) if let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId { peerIds = .associated(peerId, associatedMessageId) } + case let .external(input): + peerIds = .external(input) } return peerIds } - public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal(userInteractive: true, { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - - var anchor: HistoryViewInputAnchor = .upperBound - switch peerIds { - case let .single(peerId): - if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { - if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) - } - } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) - } - } - case let .associated(mainId, associatedId): - var ids: [PeerId] = [] - ids.append(mainId) - if let associatedId = associatedId { - ids.append(associatedId.peerId) - } + public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocation in + return self.transactionSignal(userInteractive: true, { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) - var found = false - loop: for peerId in ids.reversed() { - if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - found = true - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) + var anchor: HistoryViewInputAnchor = .upperBound + switch peerIds { + case let .single(peerId): + if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { + if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) + } + } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) } - break loop } - } - - if !found { - if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) + case let .associated(mainId, associatedId): + var ids: [PeerId] = [] + ids.append(mainId) + if let associatedId = associatedId { + ids.append(associatedId.peerId) } + + var found = false + loop: for peerId in ids.reversed() { + if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + found = true + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) + } + break loop + } + } + + if !found { + if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) + } + } + case .external: + anchor = .upperBound } + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + }) + } + } + + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocation in + return self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) } - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) - }) - } - - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocation in + return self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) + + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + } } } - private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { + private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] - var mainPeerId: PeerId? + var mainPeerIdForTopTaggedMessages: PeerId? switch peerIds { case let .single(id): - mainPeerId = id + mainPeerIdForTopTaggedMessages = id case let .associated(id, _): - mainPeerId = id + mainPeerIdForTopTaggedMessages = id + case .external: + mainPeerIdForTopTaggedMessages = nil } - if let peerId = mainPeerId { + if let peerId = mainPeerIdForTopTaggedMessages { for namespace in topTaggedMessageIdNamespaces { if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId, namespace: namespace) { if let index = self.messageHistoryIndexTable.getIndex(messageId) { @@ -2453,6 +2491,9 @@ public final class Postbox { } } additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages)) + case let .message(id): + let message = self.getMessage(id) + additionalDataEntries.append(.message(id, message)) case let .peerChatState(peerId): additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState)) case .totalUnreadState: @@ -2491,6 +2532,8 @@ public final class Postbox { if let readState = self.readStateTable.getCombinedState(peerId) { transientReadStates = .peer([peerId: readState]) } + case .external: + transientReadStates = nil } if let fixedCombinedReadStates = fixedCombinedReadStates { @@ -2517,6 +2560,8 @@ public final class Postbox { initialData = self.initialMessageHistoryData(peerId: peerId) case let .associated(peerId, _): initialData = self.initialMessageHistoryData(peerId: peerId) + case let .external(input): + initialData = self.initialMessageHistoryData(peerId: input.peerId) } subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData)) @@ -3178,7 +3223,7 @@ public final class Postbox { var remainingLimit = limit var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace) while remainingLimit > 0 { - let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) + let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) for message in messages { let attributes = MessageHistoryTable.renderMessageAttributes(message) if !f(message.id, attributes) { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 6b7de365ee..0abae1dd7d 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -29,6 +29,9 @@ final class ViewTracker { private let messageHistoryHolesView = MutableMessageHistoryHolesView() private let messageHistoryHolesViewSubscribers = Bag>() + private let externalMessageHistoryHolesView = MutableMessageHistoryExternalHolesView() + private let externalMessageHistoryHolesViewSubscribers = Bag>() + private let chatListHolesView = MutableChatListHolesView() private let chatListHolesViewSubscribers = Bag>() @@ -309,7 +312,7 @@ final class ViewTracker { case .associated: var ids = Set() switch mutableView.peerIds { - case .single: + case .single, .external: assertionFailure() case let .associated(mainPeerId, associatedId): ids.insert(mainPeerId) @@ -326,6 +329,8 @@ final class ViewTracker { } } } + case .external: + break } mutableView.updatePeerIds(transaction: transaction) diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 600f8741f7..94145bbdb7 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -170,22 +170,22 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index 4b505dae7d..80e166ea34 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -468,6 +468,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { actionSheet?.dismissAnimated() let databasePath = context.account.basePath + "/postbox/db" let _ = try? FileManager.default.removeItem(atPath: databasePath) + exit(0) preconditionFailure() }), ]), ActionSheetItemGroup(items: [ diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index db6abc4344..47bdbbba0a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -159,7 +159,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil) - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) var node: ListViewItemNode? if let current = currentNode { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 862a27987f..e98633d1cc 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -235,22 +235,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -316,22 +316,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 2830ef49ba..8cd04d4ec1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -786,17 +786,17 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let chatNodes = self.chatNodes { @@ -852,19 +852,19 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -872,13 +872,13 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index e6a3736b1f..13125e369d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -373,24 +373,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -457,19 +457,19 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -477,13 +477,13 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index df4f629682..2a24f6b579 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -161,10 +161,10 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) if let (author, text) = messageItem.reply { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index d489119662..c6abb1ff4e 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -839,10 +839,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let theme = self.presentationData.theme.withUpdated(preview: true) - let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) diff --git a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift index cbcc36b5fa..308c0ad9c1 100644 --- a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift @@ -3,23 +3,45 @@ import Postbox public class ReplyMessageAttribute: MessageAttribute { public let messageId: MessageId + public let threadMessageId: MessageId? public var associatedMessageIds: [MessageId] { return [self.messageId] } - public init(messageId: MessageId) { + public init(messageId: MessageId, threadMessageId: MessageId?) { self.messageId = messageId + self.threadMessageId = threadMessageId } required public init(decoder: PostboxDecoder) { let namespaceAndId: Int64 = decoder.decodeInt64ForKey("i", orElse: 0) self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("p", orElse: 0)), namespace: Int32(namespaceAndId & 0xffffffff), id: Int32((namespaceAndId >> 32) & 0xffffffff)) + + if let threadNamespaceAndId = decoder.decodeOptionalInt64ForKey("ti"), let threadPeerId = decoder.decodeOptionalInt64ForKey("tp") { + self.threadMessageId = MessageId(peerId: PeerId(threadPeerId), namespace: Int32(threadNamespaceAndId & 0xffffffff), id: Int32((threadNamespaceAndId >> 32) & 0xffffffff)) + } else { + self.threadMessageId = nil + } } public func encode(_ encoder: PostboxEncoder) { let namespaceAndId = Int64(self.messageId.namespace) | (Int64(self.messageId.id) << 32) encoder.encodeInt64(namespaceAndId, forKey: "i") encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "p") + if let threadMessageId = self.threadMessageId { + let threadNamespaceAndId = Int64(threadMessageId.namespace) | (Int64(threadMessageId.id) << 32) + encoder.encodeInt64(threadNamespaceAndId, forKey: "ti") + encoder.encodeInt64(threadMessageId.peerId.toInt64(), forKey: "tp") + } + } +} + +public extension Message { + var effectiveReplyThreadMessageId: MessageId? { + if let threadId = self.threadId { + return makeThreadIdMessageId(peerId: self.id.peerId, threadId: threadId) + } + return nil } } diff --git a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift new file mode 100644 index 0000000000..7ef8c3f65c --- /dev/null +++ b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift @@ -0,0 +1,34 @@ +import Foundation +import Postbox + +public class ReplyThreadMessageAttribute: MessageAttribute { + public let count: Int32 + public let latestUsers: [PeerId] + public let commentsPeerId: PeerId? + + public var associatedPeerIds: [PeerId] { + return self.latestUsers + } + + public init(count: Int32, latestUsers: [PeerId], commentsPeerId: PeerId?) { + self.count = count + self.latestUsers = latestUsers + self.commentsPeerId = commentsPeerId + } + + required public init(decoder: PostboxDecoder) { + self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.latestUsers = decoder.decodeInt64ArrayForKey("u").map(PeerId.init) + self.commentsPeerId = decoder.decodeOptionalInt64ForKey("cp").flatMap(PeerId.init) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.count, forKey: "c") + encoder.encodeInt64Array(self.latestUsers.map { $0.toInt64() }, forKey: "u") + if let commentsPeerId = self.commentsPeerId { + encoder.encodeInt64(commentsPeerId.toInt64(), forKey: "cp") + } else { + encoder.encodeNil(forKey: "cp") + } + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index f5378c371f..f8537f462f 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -256,6 +256,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) } dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) } + dict[295679367] = { return Api.Update.parse_updateReadDiscussion($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -434,6 +435,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } + dict[-765481584] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) } dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } @@ -520,7 +522,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } dict[-914167110] = { return Api.CdnPublicKey.parse_cdnPublicKey($0) } - dict[1355885018] = { return Api.MessageReplies.parse_messageReplies($0) } + dict[-2099001323] = { return Api.MessageReplies.parse_messageReplies($0) } dict[53231223] = { return Api.InputGame.parse_inputGameID($0) } dict[-1020139510] = { return Api.InputGame.parse_inputGameShortName($0) } dict[1107543535] = { return Api.help.CountryCode.parse_countryCode($0) } @@ -542,6 +544,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[480546647] = { return Api.InputChatPhoto.parse_inputChatPhotoEmpty($0) } dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) } dict[-968723890] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) } + dict[742337250] = { return Api.messages.MessageViews.parse_messageViews($0) } dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) } dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) } dict[-1107852396] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } @@ -1144,6 +1147,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputSecureValue: _1.serialize(buffer, boxed) + case let _1 as Api.messages.DiscussionMessage: + _1.serialize(buffer, boxed) case let _1 as Api.help.DeepLinkInfo: _1.serialize(buffer, boxed) case let _1 as Api.account.WebAuthorizations: @@ -1262,6 +1267,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputChatPhoto: _1.serialize(buffer, boxed) + case let _1 as Api.messages.MessageViews: + _1.serialize(buffer, boxed) case let _1 as Api.PaymentCharge: _1.serialize(buffer, boxed) case let _1 as Api.MessageInteractionCounters: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 824abb757e..b4d1d0f2b4 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -823,6 +823,66 @@ public struct messages { } } + } + public enum DiscussionMessage: TypeConstructorDescription { + case discussionMessage(message: Api.Message, readMaxId: Int32, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .discussionMessage(let message, let readMaxId, let chats, let users): + if boxed { + buffer.appendInt32(-765481584) + } + message.serialize(buffer, true) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .discussionMessage(let message, let readMaxId, let chats, let users): + return ("discussionMessage", [("message", message), ("readMaxId", readMaxId), ("chats", chats), ("users", users)]) + } + } + + public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.DiscussionMessage.discussionMessage(message: _1!, readMaxId: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + } public enum SearchCounter: TypeConstructorDescription { case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32) @@ -1209,6 +1269,56 @@ public struct messages { } } + } + public enum MessageViews: TypeConstructorDescription { + case messageViews(views: [Api.MessageViews], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let views, let users): + if boxed { + buffer.appendInt32(742337250) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(views.count)) + for item in views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageViews(let views, let users): + return ("messageViews", [("views", views), ("users", users)]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: [Api.MessageViews]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.MessageViews.messageViews(views: _1!, users: _2!) + } + else { + return nil + } + } + } public enum PeerDialogs: TypeConstructorDescription { case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) @@ -6039,6 +6149,7 @@ public extension Api { case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) case updateChannelParticipant(flags: Int32, channelId: Int32, date: Int32, userId: Int32, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, qts: Int32) case updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32) + case updateReadDiscussion(peer: Api.Peer, msgId: Int32, readMaxId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6739,6 +6850,14 @@ public extension Api { serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(forwards, buffer: buffer, boxed: false) break + case .updateReadDiscussion(let peer, let msgId, let readMaxId): + if boxed { + buffer.appendInt32(295679367) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + break } } @@ -6910,6 +7029,8 @@ public extension Api { return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)]) case .updateChannelMessageForwards(let channelId, let id, let forwards): return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)]) + case .updateReadDiscussion(let peer, let msgId, let readMaxId): + return ("updateReadDiscussion", [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]) } } @@ -8305,6 +8426,25 @@ public extension Api { return nil } } + public static func parse_updateReadDiscussion(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadDiscussion(peer: _1!, msgId: _2!, readMaxId: _3!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -15002,13 +15142,13 @@ public extension Api { } public enum MessageReplies: TypeConstructorDescription { - case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Int32]?, channelId: Int32?, topMsgId: Int32?) + case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Int32]?, channelId: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let topMsgId): + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId): if boxed { - buffer.appendInt32(1355885018) + buffer.appendInt32(-2099001323) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(replies, buffer: buffer, boxed: false) @@ -15019,15 +15159,14 @@ public extension Api { serializeInt32(item, buffer: buffer, boxed: false) }} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let topMsgId): - return ("messageReplies", [("flags", flags), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers), ("channelId", channelId), ("topMsgId", topMsgId)]) + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId): + return ("messageReplies", [("flags", flags), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers), ("channelId", channelId)]) } } @@ -15044,16 +15183,13 @@ public extension Api { } } var _5: Int32? if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, topMsgId: _6) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index aaacc3f7e3..d7405ed468 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -2885,6 +2885,32 @@ public extension Api { }) } + public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2045448344) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + public static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1489903017) @@ -3656,40 +3682,18 @@ public extension Api { }) } - public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageViews]>) { + public static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-39035462) + buffer.appendInt32(-39505956) peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - increment.serialize(buffer, true) - return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageViews]? in - let reader = BufferReader(buffer) - var result: [Api.MessageViews]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) - } - return result - }) - } - - public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1271290010) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", peer), ("msgId", msgId), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in let reader = BufferReader(buffer) var result: Api.messages.Messages? if let signature = reader.readInt32() { @@ -3699,24 +3703,69 @@ public extension Api { }) } - public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1310163211) - serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(1147761405) peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", peer), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.DiscussionMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage + } + return result + }) + } + + public static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-147740172) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + + public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1468322785) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + increment.serialize(buffer, true) + return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews + } + return result + }) + } + + public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1934479725) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("topMsgId", topMsgId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("filter", filter), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in let reader = BufferReader(buffer) var result: Api.messages.Messages? if let signature = reader.readInt32() { @@ -5411,6 +5460,34 @@ public extension Api { }) } + public static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1063816159) + + return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in + let reader = BufferReader(buffer) + var result: Api.help.PromoData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PromoData + } + return result + }) + } + + public static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(505748629) + peer.serialize(buffer, true) + return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + public static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1723407216) @@ -5445,34 +5522,6 @@ public extension Api { }) } - public static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1063816159) - - return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in - let reader = BufferReader(buffer) - var result: Api.help.PromoData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PromoData - } - return result - }) - } - - public static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(505748629) - peer.serialize(buffer, true) - return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } - public static func dismissSuggestion(suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(125807007) @@ -6859,14 +6908,11 @@ public extension Api { }) } - public static func uploadProfilePhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1980559511) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", flags), ("file", file), ("video", video), ("videoStartTs", videoStartTs)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + buffer.appendInt32(1926525996) + id.serialize(buffer, true) + return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in let reader = BufferReader(buffer) var result: Api.photos.Photo? if let signature = reader.readInt32() { @@ -6876,11 +6922,14 @@ public extension Api { }) } - public static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func uploadProfilePhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1926525996) - id.serialize(buffer, true) - return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + buffer.appendInt32(-1980559511) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", flags), ("file", file), ("video", video), ("videoStartTs", videoStartTs)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in let reader = BufferReader(buffer) var result: Api.photos.Photo? if let signature = reader.readInt32() { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index afdac90c77..be425eb9b7 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -557,7 +557,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index d2a4eea491..e604d4f956 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -30,6 +30,7 @@ private var declaredEncodables: Void = { declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) }) declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) }) declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) }) + declareEncodable(ReplyThreadMessageAttribute.self, f: { ReplyThreadMessageAttribute(decoder: $0) }) declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) }) declareEncodable(PendingReactionsMessageAttribute.self, f: { PendingReactionsMessageAttribute(decoder: $0) }) declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index 57c725bf36..327a64973b 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -975,7 +975,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.authorizationListUpdated = true } - let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) + let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) updatedState.addMessages([message], location: .UpperHistoryBlock) } } @@ -2274,9 +2274,44 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var topUpperHistoryBlockMessages: [PeerIdAndMessageNamespace: MessageId.Id] = [:] + final class MessageThreadStatsRecord { + var count: Int = 0 + var peers: [PeerId] = [] + } + var messageThreadStatsDifferences: [MessageId: MessageThreadStatsRecord] = [:] + func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int, addedMessagePeer: PeerId?) { + if let value = messageThreadStatsDifferences[threadMessageId] { + value.count += add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } + } else { + let value = MessageThreadStatsRecord() + messageThreadStatsDifferences[threadMessageId] = value + value.count = add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } + } + } + for operation in optimizedOperations(finalState.state.operations) { switch operation { case let .AddMessages(messages, location): + if case .UpperHistoryBlock = location { + for message in messages { + if case let .Id(id) = message.id { + if let threadId = message.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if !transaction.messageExists(id: id) { + addMessageThreadStatsDifference(threadMessageId: messageThreadId, add: 1, remove: 0, addedMessagePeer: message.authorId) + } + } + } + } + } + } let _ = transaction.addMessages(messages, location: location) if case .UpperHistoryBlock = location { for message in messages { @@ -2384,7 +2419,9 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } case let .DeleteMessages(ids): - deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids) + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in + addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil) + }) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false) @@ -2775,7 +2812,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateMessageForwardsCount(id, count): transaction.updateMessage(id, update: { currentMessage in @@ -2790,7 +2827,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateInstalledStickerPacks(operation): stickerPackOperations.append(operation) @@ -2908,6 +2945,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } } + for (threadMessageId, difference) in messageThreadStatsDifferences { + updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference.count, addedMessagePeers: difference.peers) + } + if !peerActivityTimestamps.isEmpty { updatePeerPresenceLastActivities(transaction: transaction, accountPeerId: accountPeerId, activities: peerActivityTimestamps) } diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index 4c78b36787..6aeb2c821f 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -180,7 +180,7 @@ private func fetchPoll(account: Account, messageId: MessageId) -> Signal [AdditionalMessageHistoryViewData] { +private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] { var result = additionalData switch chatLocation { case let .peer(peerId): @@ -189,6 +189,12 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additi result.append(.peerChatState(peerId)) } } + case let .external(peerId, _): + if peerId.namespace == Namespaces.Peer.CloudChannel { + if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { + result.append(.peerChatState(peerId)) + } + } } return result } @@ -375,7 +381,7 @@ public final class AccountViewTracker { break } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) }) } } @@ -591,34 +597,63 @@ public final class AccountViewTracker { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue)) |> map(Optional.init) - |> `catch` { _ -> Signal<[Api.MessageViews]?, NoError> in + |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { viewCounts -> Signal in - if let viewCounts = viewCounts { - return account.postbox.transaction { transaction -> Void in - for i in 0 ..< messageIds.count { - if i < viewCounts.count { - if case let .messageViews(_, views, forwards, _) = viewCounts[i] { - transaction.updateMessage(messageIds[i], update: { currentMessage in - let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) - var attributes = currentMessage.attributes - loop: for j in 0 ..< attributes.count { - if let attribute = attributes[j] as? ViewCountMessageAttribute, let views = views { - attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views))) + |> mapToSignal { result -> Signal in + guard case let .messageViews(viewCounts, _)? = result else { + return .complete() + } + + return account.postbox.transaction { transaction -> Void in + for i in 0 ..< messageIds.count { + if i < viewCounts.count { + if case let .messageViews(_, views, forwards, replies) = viewCounts[i] { + transaction.updateMessage(messageIds[i], update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + var foundReplies = false + var commentsChannelId: PeerId? + var recentRepliersPeerIds: [PeerId]? + var repliesCount: Int32? + if let replies = replies { + switch replies { + case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId): + if let channelId = channelId { + commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } - if let _ = attributes[j] as? ForwardCountMessageAttribute, let forwards = forwards { - attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) + repliesCount = repliesCountValue + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } + } else { + recentRepliersPeerIds = nil } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) - } + } + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ViewCountMessageAttribute { + if let views = views { + attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views))) + } + } else if let _ = attributes[j] as? ForwardCountMessageAttribute { + if let forwards = forwards { + attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) + } + } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { + foundReplies = true + if let repliesCount = repliesCount { + attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId) + } + } + } + if !foundReplies, let repliesCount = repliesCount { + attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId)) + } + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) } } } - } else { - return .complete() } } } else { @@ -940,7 +975,7 @@ public final class AccountViewTracker { break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, 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 .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)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) @@ -1073,7 +1108,7 @@ public final class AccountViewTracker { } } - func wrappedMessageHistorySignal(chatLocation: ChatLocation, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + func wrappedMessageHistorySignal(chatLocation: ChatLocationInput, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { let history = withState(signal, { [weak self] () -> Int32 in if let strongSelf = self { return OSAtomicIncrement32(&strongSelf.nextViewId) @@ -1102,6 +1137,10 @@ public final class AccountViewTracker { if peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) } + case let .external(peerId, _): + if peerId.namespace == Namespaces.Peer.CloudChannel { + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + } } } } @@ -1148,7 +1187,7 @@ public final class AccountViewTracker { } } - public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: nil, namespaces: .just(Namespaces.Message.allScheduled), orderStatistics: [], additionalData: additionalData) return withState(signal, { [weak self] () -> Int32 in @@ -1178,7 +1217,7 @@ public final class AccountViewTracker { } } - public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: true) @@ -1187,7 +1226,7 @@ public final class AccountViewTracker { } } - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: false) @@ -1196,7 +1235,7 @@ public final class AccountViewTracker { } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let inputAnchor: HistoryViewInputAnchor switch index { diff --git a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift index 84974b2b75..6de3383d3a 100644 --- a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift @@ -32,7 +32,7 @@ func applyMaxReadIndexInteractively(transaction: Transaction, stateManager: Acco return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } @@ -101,7 +101,7 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index 9d09f0de6e..be58039f8e 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -42,7 +42,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: } } -func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal { +func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates, accountPeerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in let messageId: Int32? var apiMessage: Api.Message? @@ -90,6 +90,8 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes transaction.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp) } + var updatedMessage: StoreMessage? + transaction.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId if let messageId = messageId { @@ -222,8 +224,19 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } } - return .update(StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) + let updatedMessageValue = StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media) + updatedMessage = updatedMessageValue + + return .update(updatedMessageValue) }) + if let updatedMessage = updatedMessage, case let .Id(updatedId) = updatedMessage.id { + if message.id.namespace == Namespaces.Message.Local && updatedId.namespace == Namespaces.Message.Cloud && updatedId.peerId.namespace == Namespaces.Peer.CloudChannel { + if let threadId = updatedMessage.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId) + updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [accountPeerId]) + } + } + } for file in sentStickers { transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } @@ -288,20 +301,21 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage var sentStickers: [TelegramMediaFile] = [] var sentGifs: [TelegramMediaFile] = [] - var updatedGroupingKey: Int64? - for (_, _, updatedMessage) in mapping { - if let updatedGroupingKey = updatedGroupingKey { - assert(updatedGroupingKey == updatedMessage.groupingKey) + var updatedGroupingKey: [Int64 : [MessageId]] = [:] + for (message, _, updatedMessage) in mapping { + if let groupingKey = updatedMessage.groupingKey { + var ids = updatedGroupingKey[groupingKey] ?? [] + ids.append(message.id) + updatedGroupingKey[groupingKey] = ids } - updatedGroupingKey = updatedMessage.groupingKey } if let latestPreviousId = latestPreviousId, let latestIndex = mapping.last?.1 { transaction.offsetPendingMessagesTimestamps(lowerBound: latestPreviousId, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp) } - if let updatedGroupingKey = updatedGroupingKey { - transaction.updateMessageGroupingKeysAtomically(mapping.map { $0.0.id }, groupingKey: updatedGroupingKey) + for (key, ids) in updatedGroupingKey { + transaction.updateMessageGroupingKeysAtomically(ids, groupingKey: key) } for (message, _, updatedMessage) in mapping { @@ -360,7 +374,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities) - return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) + return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) }) } diff --git a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift index dfb6619eaa..5c3cbde19c 100644 --- a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift @@ -102,7 +102,8 @@ private final class HistoryPreloadEntry: Comparable { |> mapToSignal { download -> Signal in switch hole.hole { case let .peer(peerHole): - return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60) + return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, threadId: nil, count: 60) + |> ignoreValues } } ) diff --git a/submodules/TelegramCore/Sources/DeleteMessages.swift b/submodules/TelegramCore/Sources/DeleteMessages.swift index 14dfab3fd3..c8c06b4de4 100644 --- a/submodules/TelegramCore/Sources/DeleteMessages.swift +++ b/submodules/TelegramCore/Sources/DeleteMessages.swift @@ -23,7 +23,7 @@ func addMessageMediaResourceIdsToRemove(message: Message, resourceIds: inout [Wr } } -public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true) { +public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true, manualAddMessageThreadStatsDifference: ((MessageId, Int, Int) -> Void)? = nil) { var resourceIds: [WrappedMediaResourceId] = [] if deleteMedia { for id in ids { @@ -37,6 +37,22 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M if !resourceIds.isEmpty { let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } + for id in ids { + if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud { + if let message = transaction.getMessage(id) { + if let threadId = message.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if let manualAddMessageThreadStatsDifference = manualAddMessageThreadStatsDifference { + manualAddMessageThreadStatsDifference(messageThreadId, 0, 1) + } else { + updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: -1, addedMessagePeers: []) + } + } + } + } + } + } transaction.deleteMessages(ids, forEachMedia: { _ in }) } diff --git a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift index 12a257f601..e206ada74b 100644 --- a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift +++ b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift @@ -114,7 +114,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId, type: In } if let topIndex = topIndex { if peerId.namespace == Namespaces.Peer.CloudUser { - let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) + let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) } else { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false) } diff --git a/submodules/TelegramCore/Sources/EnqueueMessage.swift b/submodules/TelegramCore/Sources/EnqueueMessage.swift index b1fc81bf18..4f23a58f68 100644 --- a/submodules/TelegramCore/Sources/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/EnqueueMessage.swift @@ -316,7 +316,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, attributes.append(contentsOf: filterMessageAttributesForOutgoingMessage(requestedAttributes)) if let replyToMessageId = replyToMessageId, replyToMessageId.peerId == peerId { - attributes.append(ReplyMessageAttribute(messageId: replyToMessageId)) + var threadMessageId: MessageId? + if let replyMessage = transaction.getMessage(replyToMessageId) { + threadMessageId = replyMessage.effectiveReplyThreadMessageId + } + attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: threadMessageId)) } var mediaList: [Media] = [] if let mediaReference = mediaReference { @@ -408,7 +412,18 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) + var threadId: Int64? + if let replyToMessageId = replyToMessageId { + if let message = transaction.getMessage(replyToMessageId) { + if let threadIdValue = message.threadId { + threadId = threadIdValue + } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + threadId = makeMessageThreadId(replyToMessageId) + } + } + } + + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) case let .forward(source, grouping, requestedAttributes): let sourceMessage = transaction.getMessage(source) if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] { @@ -523,16 +538,20 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var messageNamespace = Namespaces.Message.Local var entitiesAttribute: TextEntitiesMessageAttribute? var effectiveTimestamp = timestamp + var threadId: Int64? for attribute in attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { entitiesAttribute = attribute - } - if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { + } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { if attribute.scheduleTime == scheduleWhenOnlineTimestamp, let presence = peerPresence as? TelegramUserPresence, case let .present(statusTimestamp) = presence.status, statusTimestamp >= timestamp { } else { messageNamespace = Namespaces.Message.ScheduledLocal effectiveTimestamp = attribute.scheduleTime } + } else if let attribute = attribute as? ReplyMessageAttribute { + if let threadMessageId = attribute.threadMessageId { + threadId = makeMessageThreadId(threadMessageId) + } } } @@ -568,7 +587,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat) } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) } } } diff --git a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift index 50fb705fc7..590ad6d6d1 100644 --- a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift @@ -138,7 +138,7 @@ final class HistoryViewStateValidationContexts { self.accountPeerId = accountPeerId } - func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocation? = nil) { + func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) { assert(self.queue.isCurrent()) guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.music else { if self.contexts[id] != nil { @@ -600,7 +600,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran let updatedFlags = StoreMessageFlags(currentMessage.flags) - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) } }) @@ -640,7 +640,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -673,7 +673,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } else { deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id]) @@ -708,7 +708,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } diff --git a/submodules/TelegramCore/Sources/Holes.swift b/submodules/TelegramCore/Sources/Holes.swift index c36dae48cc..7fa7502bfd 100644 --- a/submodules/TelegramCore/Sources/Holes.swift +++ b/submodules/TelegramCore/Sources/Holes.swift @@ -42,8 +42,8 @@ enum FetchMessageHistoryHoleSource { } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> Void) -> Signal { - return postbox.transaction { transaction -> Signal in +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { + return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedIds = Set() for message in storeMessages { @@ -59,8 +59,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds)) if referencedIds.isEmpty { - f(transaction, [], []) - return .complete() + return .single(f(transaction, [], [])) } else { var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) { @@ -97,7 +96,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor let fetchMessages = combineLatest(signals) return fetchMessages - |> mapToSignal { results -> Signal in + |> mapToSignal { results -> Signal in var additionalPeers: [Peer] = [] var additionalMessages: [StoreMessage] = [] for (messages, chats, users) in results { @@ -117,17 +116,16 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor additionalPeers.append(TelegramUser(user: user)) } } - return postbox.transaction { transaction -> Void in - f(transaction, additionalPeers, additionalMessages) + return postbox.transaction { transaction -> T in + return f(transaction, additionalPeers, additionalMessages) } - |> ignoreValues } } } |> switchToLatest } -func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal { +func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal { let count = min(100, rawCount) return postbox.stateView() @@ -139,7 +137,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } |> take(1) - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return postbox.loadedPeerWithId(peerId) |> take(1) |> mapToSignal { peer in @@ -152,52 +150,102 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch space { case .everywhere: - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - - let rangeStartId = start.id - let rangeEndId = min(end.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId + if let threadId = threadId { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } - } else { - offsetId = start.id == Int32.max ? start.id : (start.id + 1) - addOffset = 0 - maxId = start.id == Int32.max ? start.id : (start.id + 1) - minId = end.id - - let rangeStartId = end.id - let rangeEndId = min(start.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + + minMaxRange = 1 ... (Int32.max - 1) + } + + request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + } else { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } - } - case let .aroundId(id): - offsetId = id.id - addOffset = Int32(-selectedLimit / 2) - maxId = Int32.max - minId = 1 - minMaxRange = 1 ... Int32.max - 1 + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + minMaxRange = 1 ... Int32.max - 1 + } + + request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) } - - request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) case let .tag(tag): assert(tag.containsSingleElement) if tag == .unseenPersonalMessage { @@ -314,7 +362,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH return request |> retryRequest - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] @@ -368,7 +416,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages in + return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> IndexSet in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) let filledRange: ClosedRange @@ -408,16 +456,19 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } } - transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) + + if threadId == nil { + transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) + } updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in return updated }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) done") + print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) threadId: \(String(describing: threadId)) done") - return + return IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)) }) } } else { @@ -504,7 +555,10 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId for (groupId, summary) in fetchedChats.folderSummaries { transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary) } + + return }) + |> ignoreValues } } diff --git a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift index d24d8915ef..ec862edad0 100644 --- a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift @@ -49,7 +49,7 @@ public func installInteractiveReadMessagesAction(postbox: Postbox, stateManager: break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, 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 .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)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) diff --git a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift index fd094efc44..76c4ac24b6 100644 --- a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift +++ b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift @@ -74,7 +74,7 @@ func managedAutoremoveMessageOperations(postbox: Postbox) -> Signal ignoreValues @@ -220,7 +220,7 @@ private func synchronizeMessageReactions(transaction: Transaction, postbox: Post break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } |> ignoreValues diff --git a/submodules/TelegramCore/Sources/MessageUtils.swift b/submodules/TelegramCore/Sources/MessageUtils.swift index 1f09a7143a..a4a9ec6003 100644 --- a/submodules/TelegramCore/Sources/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/MessageUtils.swift @@ -168,7 +168,7 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes } } - return Message(stableId: 0, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + return Message(stableId: 0, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } public extension Message { diff --git a/submodules/TelegramCore/Sources/PendingMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessageManager.swift index a1cc5d6fc5..500e9f32e8 100644 --- a/submodules/TelegramCore/Sources/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessageManager.swift @@ -111,7 +111,7 @@ private func failMessages(postbox: Postbox, ids: [MessageId]) -> Signal if isForward { - if !messages.contains(where: { $0.0.groupingKey == nil }) { + if messages.contains(where: { $0.0.groupingKey != nil }) { flags |= (1 << 9) } @@ -906,7 +906,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } else { let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state) @@ -922,7 +922,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { @@ -931,7 +931,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1072,7 +1072,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) }).start() } @@ -1086,7 +1086,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1114,7 +1114,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -1135,7 +1135,7 @@ public final class PendingMessageManager { namespace = id.namespace } - return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) + return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result, accountPeerId: self.accountPeerId) |> afterDisposed { [weak self] in if let strongSelf = self { strongSelf.queue.async { diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index 16b490b314..9feef62447 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -352,7 +352,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) @@ -641,7 +641,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + 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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) diff --git a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift index b86d428398..431af1370b 100644 --- a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift @@ -512,7 +512,7 @@ extension StoreMessage { convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) { switch apiMessage { case let .decryptedMessage(randomId, _, message, media): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) case let .decryptedMessageService(randomId, _, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -524,9 +524,9 @@ extension StoreMessage { case let .decryptedMessageActionReadMessages(randomIds): return nil case .decryptedMessageActionScreenshotMessages: - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) } } } @@ -809,7 +809,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -822,7 +822,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -834,9 +834,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1041,7 +1041,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1054,7 +1054,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1066,9 +1066,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1279,7 +1279,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1292,7 +1292,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1304,9 +1304,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift new file mode 100644 index 0000000000..25507264eb --- /dev/null +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -0,0 +1,236 @@ +import Foundation +import SyncCore +import Postbox +import SwiftSignalKit +import TelegramApi + +private class ReplyThreadHistoryContextImpl { + private let queue: Queue + private let account: Account + private let messageId: MessageId + + private var currentHole: (MessageHistoryHolesViewEntry, Disposable)? + + struct State: Equatable { + var messageId: MessageId + var holeIndices: [MessageId.Namespace: IndexSet] + var maxReadMessageId: MessageId? + } + + let state = Promise() + private var stateValue: State { + didSet { + if self.stateValue != oldValue { + self.state.set(.single(self.stateValue)) + } + } + } + + private var holesDisposable: Disposable? + private let readDisposable = MetaDisposable() + + init(queue: Queue, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { + self.queue = queue + self.account = account + self.messageId = messageId + + self.stateValue = State(messageId: self.messageId, holeIndices: [Namespaces.Message.Cloud: IndexSet(integersIn: 1 ..< Int(Int32.max))], maxReadMessageId: maxReadMessageId) + self.state.set(.single(self.stateValue)) + + let threadId = makeMessageThreadId(messageId) + + self.holesDisposable = (account.postbox.messageHistoryHolesView() + |> map { view -> MessageHistoryHolesViewEntry? in + for entry in view.entries { + switch entry.hole { + case let .peer(hole): + if hole.threadId == threadId { + return entry + } + } + } + return nil + } + |> distinctUntilChanged + |> deliverOn(self.queue)).start(next: { [weak self] entry in + guard let strongSelf = self else { + return + } + strongSelf.setCurrentHole(entry: entry) + }) + } + + deinit { + self.holesDisposable?.dispose() + self.readDisposable.dispose() + } + + func setCurrentHole(entry: MessageHistoryHolesViewEntry?) { + if self.currentHole?.0 != entry { + self.currentHole?.1.dispose() + if let entry = entry { + self.currentHole = (entry, self.fetchHole(entry: entry).start(next: { [weak self] removedHoleIndices in + guard let strongSelf = self else { + return + } + if var currentHoles = strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] { + currentHoles.subtract(removedHoleIndices) + strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] = currentHoles + } + })) + } else { + self.currentHole = nil + } + } + } + + private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal { + switch entry.hole { + case let .peer(hole): + let fetchCount = min(entry.count, 100) + return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount) + } + } + + func applyMaxReadIndex(messageIndex: MessageIndex) { + let account = self.account + let messageId = self.messageId + + if messageIndex.id.namespace != messageId.namespace { + return + } + + let signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageIndex.id.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .complete() + } + return account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: messageId.id, readMaxId: messageIndex.id.id)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues + } + self.readDisposable.set(signal.start()) + } +} + +public class ReplyThreadHistoryContext { + fileprivate final class GuardReference { + private let deallocated: () -> Void + + init(deallocated: @escaping () -> Void) { + self.deallocated = deallocated + } + + deinit { + self.deallocated() + } + } + + private let queue = Queue() + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + let stateDisposable = impl.state.get().start(next: { state in + subscriber.putNext(MessageHistoryViewExternalInput( + peerId: state.messageId.peerId, + threadId: makeMessageThreadId(state.messageId), + maxReadMessageId: state.maxReadMessageId, + holes: state.holeIndices + )) + }) + disposable.set(stateDisposable) + } + + return disposable + } + } + + public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxReadMessageId: MessageId?) { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId, maxReadMessageId: maxReadMessageId) + }) + } + + public func applyMaxReadIndex(messageIndex: MessageIndex) { + self.impl.with { impl in + impl.applyMaxReadIndex(messageIndex: messageIndex) + } + } +} + +public struct ChatReplyThreadMessage { + public var messageId: MessageId + public var maxReadMessageId: MessageId? + + public init(messageId: MessageId, maxReadMessageId: MessageId?) { + self.messageId = messageId + self.maxReadMessageId = maxReadMessageId + } +} + +public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .single(nil) + } + return account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .single(nil) + } + return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in + switch result { + case let .discussionMessage(message, readMaxId, chats, users): + guard let parsedMessage = StoreMessage(apiMessage: message), let parsedIndex = parsedMessage.index else { + return nil + } + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + let _ = transaction.addMessages([parsedMessage], location: .Random) + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + return ChatReplyThreadMessage( + messageId: parsedIndex.id, + maxReadMessageId: MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) + ) + } + } + } + } +} diff --git a/submodules/TelegramCore/Sources/RequestEditMessage.swift b/submodules/TelegramCore/Sources/RequestEditMessage.swift index 495e7401c0..e266c1bc41 100644 --- a/submodules/TelegramCore/Sources/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/RequestEditMessage.swift @@ -295,7 +295,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan } var updatedLocalTags = currentMessage.localTags updatedLocalTags.remove(.OutgoingLiveLocation) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + 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: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index f23888dd07..a3c26184f9 100644 --- a/submodules/TelegramCore/Sources/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/SearchMessages.swift @@ -9,7 +9,7 @@ import SyncCore public enum SearchMessagesLocation: Equatable { case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case group(PeerGroupId) - case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) + case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?) case publicForwards(messageId: MessageId, datacenterId: Int?) } @@ -187,7 +187,7 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult public func searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> switch location { - case let .peer(peerId, fromId, tags, minDate, maxDate): + case let .peer(peerId, fromId, tags, topMsgId, minDate, maxDate): if peerId.namespace == Namespaces.Peer.SecretChat { return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var readStates: [PeerId: CombinedPeerReadState] = [:] @@ -330,7 +330,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q |> mapToSignal { result, additionalResult -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> in return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var additional: SearchMessagesPeerState? = mergedState(transaction: transaction, state: state?.additional, result: additionalResult) - if state?.additional == nil, case let .general(tags, minDate, maxDate) = location { + if state?.additional == nil, case let .general(tags, _, _) = location { let secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) var readStates: [PeerId: CombinedPeerReadState] = [:] for message in secretMessages { diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 18e0160d0e..726ed01499 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -514,8 +514,19 @@ extension StoreMessage { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil)) } + var threadId: Int64? if let replyToMsgId = replyToMsgId { - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) + var threadMessageId: MessageId? + if let replyToTopId = replyToTopId { + let threadIdValue = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + let threadIdValue = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) } if namespace != Namespaces.Message.ScheduledCloud { @@ -564,6 +575,23 @@ extension StoreMessage { attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) }*/ + if let replies = replies { + let recentRepliersPeerIds: [PeerId]? + //messageReplies flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?int top_msg_id:flags.2?int = MessageReplies; + switch replies { + case let .messageReplies(_, repliesCount, _, recentRepliers, channelId): + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } + } else { + recentRepliersPeerIds = nil + } + + let commentsPeerId = channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) } + + attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsPeerId)) + } + } + if let restrictionReason = restrictionReason { attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:)))) } @@ -604,7 +632,7 @@ extension StoreMessage { storeFlags.insert(.CanBeGroupedIntoFeed) - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) case .messageEmpty: return nil case let .messageService(flags, id, fromId, toId, replyToMsgId, date, action): @@ -636,7 +664,7 @@ extension StoreMessage { var attributes: [MessageAttribute] = [] if let replyToMsgId = replyToMsgId { - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: nil)) } if (flags & (1 << 17)) != 0 { @@ -673,7 +701,7 @@ extension StoreMessage { storeFlags.insert(.WasScheduled) } - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) } } } diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 62e433f3f2..3c304d59e1 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -24,7 +24,74 @@ func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } + +func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId]) { + updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: threadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: false) +} + +private func updateMessageThreadStatsInternal(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId], allowChannel: Bool) { + guard let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel else { + return + } + var isGroup = true + if case .broadcast = channel.info { + isGroup = false + if !allowChannel { + return + } + } + + var channelThreadMessageId: MessageId? + + func mergeLatestUsers(current: [PeerId], added: [PeerId], isGroup: Bool, isEmpty: Bool) -> [PeerId] { + if isEmpty { + return [] + } + if isGroup { + return current + } + var current = current + for i in 0 ..< min(3, added.count) { + let peerId = added[added.count - 1 - i] + if let index = current.firstIndex(of: peerId) { + current.remove(at: index) + current.insert(peerId, at: 0) + } else { + if current.count >= 3 { + current.removeLast() + } + current.insert(peerId, at: 0) + } + } + return current + } + + transaction.updateMessage(threadMessageId, update: { currentMessage in + let countDifference = Int32(difference) + + var attributes = currentMessage.attributes + var updated = false + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReplyThreadMessageAttribute { + let count = max(0, attribute.count + countDifference) + attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId) + updated = true + } else if let attribute = attributes[j] as? SourceReferenceMessageAttribute { + channelThreadMessageId = attribute.messageId + } + } + if !updated && isGroup { + let count = max(0, countDifference) + attributes.append(ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: [], added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), commentsPeerId: nil)) + } + 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)) + }) + + if let channelThreadMessageId = channelThreadMessageId { + updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: channelThreadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: true) + } +} diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 065fbfd7b5..12d70c6efc 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -162,6 +162,10 @@ public final class PrincipalThemeEssentialGraphics { public let outgoingDateAndStatusImpressionIcon: UIImage public let mediaImpressionIcon: UIImage public let freeImpressionIcon: UIImage + public let incomingDateAndStatusRepliesIcon: UIImage + public let outgoingDateAndStatusRepliesIcon: UIImage + public let mediaRepliesIcon: UIImage + public let freeRepliesIcon: UIImage public let dateStaticBackground: UIImage public let dateFloatingBackground: UIImage @@ -315,6 +319,12 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! + let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! + self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! + self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! + self.radialIndicatorFileIconIncoming = emptyImage self.radialIndicatorFileIconOutgoing = emptyImage } else { @@ -410,6 +420,12 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! + let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! + self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! + self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! + self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 8ba11022da..37d8ae4e19 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -256,4 +256,7 @@ public enum PresentationResourceParameterKey: Hashable { case chatMessageLike(incoming: Bool, isSelected: Bool) case chatMessageFreeLike(isSelected: Bool) case chatMessageMediaLike(isSelected: Bool) + + case chatMessageCommentsIcon(incoming: Bool) + case chatMessageCommentsArrowIcon(incoming: Bool) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 42eaae3348..b0dabb25db 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1090,4 +1090,20 @@ public struct PresentationResourcesChat { } }) } + + public static func chatMessageCommentsIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatMessageCommentsIcon(incoming: incoming), { theme in + let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing + + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BubbleComments"), color: messageTheme.accentTextColor) + }) + } + + public static func chatMessageCommentsArrowIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatMessageCommentsArrowIcon(incoming: incoming), { theme in + let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing + + return generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: messageTheme.accentTextColor) + }) + } } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json new file mode 100644 index 0000000000..e381a41f26 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_viewreplies.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf new file mode 100644 index 0000000000..db1722552c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json new file mode 100644 index 0000000000..adb3950937 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_leaveacomment (1).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf new file mode 100644 index 0000000000..26a4e6487b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json new file mode 100644 index 0000000000..358678e10d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "replies.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf new file mode 100644 index 0000000000..f55a72204e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf differ diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index c74900e0b3..919fac2fdc 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -298,6 +298,45 @@ public final class AccountContextImpl: AccountContext { return nil } } + + public func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput { + switch location { + case let .peer(peerId): + return .peer(peerId) + case let .replyThread(messageId, maxReadMessageId): + let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) + return .external(messageId.peerId, context.state) + } + } + + public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) { + switch location { + case .peer: + let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start() + case let .replyThread(messageId, maxReadMessageId): + let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) + context.applyMaxReadIndex(messageIndex: messageIndex) + } + } +} + +private func chatLocationContext(holder: Atomic, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) -> ReplyThreadHistoryContext { + let holder = holder.modify { current in + if let current = current as? ChatLocationContextHolderImpl { + return current + } else { + return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxReadMessageId: maxReadMessageId) + } + } as! ChatLocationContextHolderImpl + return holder.context +} + +private final class ChatLocationContextHolderImpl: ChatLocationContextHolder { + let context: ReplyThreadHistoryContext + + init(account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { + self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxReadMessageId: maxReadMessageId) + } } func getAppConfiguration(transaction: Transaction) -> AppConfiguration { diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index 33a435c828..09abf8122a 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -46,7 +46,11 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? { } } } else { - return nil + if isMuted { + return .unmuteNotifications + } else { + return .muteNotifications + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 709780db30..736d6de3b4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -61,6 +61,17 @@ import TelegramIntents import TooltipUI import StatisticsUI +extension ChatLocation { + var peerId: PeerId { + switch self { + case let .peer(peerId): + return peerId + case let .replyThread(messageId, _): + return messageId.peerId + } + } +} + public enum ChatControllerPeekActions { case standard case remove(() -> Void) @@ -326,6 +337,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var hasEmbeddedTitleContent = false private var isEmbeddedTitleContentHidden = false + + private let chatLocationContextHolder = Atomic(value: nil) public override var customData: Any? { return self.chatLocation @@ -350,9 +363,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .peer(peerId): locationBroadcastPanelSource = .peer(peerId) self.chatLocationInfoData = .peer(Promise()) - /*case .group: + case .replyThread: locationBroadcastPanelSource = .none - self.chatLocationInfoData = .group(Promise())*/ + self.chatLocationInfoData = .peer(Promise()) } self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1082,8 +1095,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if (peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup) { postAsReply = true } - /*case .group: - postAsReply = true*/ + case .replyThread: + postAsReply = true } } @@ -1140,11 +1153,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .single(nil) } } - } else if case let .peer(peerId) = strongSelf.chatLocation { - resolveSignal = context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) } else { - resolveSignal = .single(nil) + resolveSignal = context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) + |> map(Optional.init) } var cancelImpl: (() -> Void)? let presentationData = strongSelf.presentationData @@ -1424,12 +1435,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet?.dismissAnimated() if let strongSelf = self { let peerSignal: Signal - if case let .peer(peerId) = strongSelf.chatLocation { - peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) - } else { - peerSignal = .single(nil) - } + peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) + |> map(Optional.init) let _ = (peerSignal |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self { @@ -1605,6 +1612,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch strongSelf.chatLocation { case let .peer(peerId): strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) + case let .replyThread(messageId, _): + let peerId = messageId.peerId + strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) } }, requestRedeliveryOfFailedMessages: { [weak self] id in guard let strongSelf = self else { @@ -2148,6 +2158,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) + }, openMessageReplies: { [weak self] messageId in + guard let strongSelf = self else { + return + } + + let foundIndex = Promise() + foundIndex.set(fetchChannelReplyThreadMessage(account: strongSelf.context.account, messageId: messageId)) + + var cancelImpl: (() -> Void)? + let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + strongSelf.present(statusController, in: .window(.root)) + + let disposable = (foundIndex.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statusController] result in + statusController?.dismiss() + + guard let strongSelf = self else { + return + } + + if let result = result { + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: result.messageId, maxReadMessageId: result.maxReadMessageId), keepStack: .always)) + } + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -2207,7 +2251,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatInfoButtonItem: UIBarButtonItem switch chatLocation { - case .peer: + case .peer, .replyThread: let avatarNode = ChatAvatarNavigationNode() avatarNode.chatController = self avatarNode.contextAction = { [weak self] node, gesture in @@ -2278,290 +2322,315 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) + let chatLocationPeerId: PeerId switch chatLocation { - case let .peer(peerId): - if case let .peer(peerView) = self.chatLocationInfoData { - peerView.set(context.account.viewTracker.peerView(peerId)) - var onlineMemberCount: Signal = .single(nil) - var hasScheduledMessages: Signal = .single(false) - - if peerId.namespace == Namespaces.Peer.CloudChannel { - let recentOnlineSignal: Signal = peerView.get() - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { - return true - } else { - return false - } - } else { - return false - } - } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } - } else { - return .single(nil) - } - } - onlineMemberCount = recentOnlineSignal - - self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in - if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { + case let .peer(peerId): + chatLocationPeerId = peerId + case let .replyThread(messageId, _): + chatLocationPeerId = messageId.peerId + } + + do { + let peerId = chatLocationPeerId + if case let .peer(peerView) = self.chatLocationInfoData { + peerView.set(context.account.viewTracker.peerView(peerId)) + var onlineMemberCount: Signal = .single(nil) + var hasScheduledMessages: Signal = .single(false) + + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { return true } else { return false } - }) - } else { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) + } else { + return false + } } - - if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat { - hasScheduledMessages = peerView.get() - |> take(1) - |> mapToSignal { view -> Signal in - if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { - return .single(false) + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) } else { - return context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation) - |> map { view, _, _ in - return !view.entries.isEmpty + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + + self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in + if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { + return true + } else { + return false + } + }) + } else { + self.reportIrrelvantGeoNoticePromise.set(.single(nil)) + } + + if case .peer = chatLocation, !isScheduledMessages, peerId.namespace != Namespaces.Peer.SecretChat { + let chatLocationContextHolder = self.chatLocationContextHolder + hasScheduledMessages = peerView.get() + |> take(1) + |> mapToSignal { view -> Signal in + if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { + return .single(false) + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) + |> map { view, _, _ in + return !view.entries.isEmpty + } + } + } + } + + let isReplyThread: Bool + let replyThreadType: ChatTitleContent.ReplyThreadType? + switch chatLocation { + case let .peer(peerId): + //TODO:localize + isReplyThread = peerId.id == 708513 + replyThreadType = nil + case let .replyThread(_, readMessageId): + isReplyThread = true + if readMessageId != nil { + replyThreadType = .comments + } else { + replyThreadType = .replies + } + } + + self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in + if let strongSelf = self { + if let peer = peerViewMainPeer(peerView) { + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, repleThread: replyThreadType) + let imageOverride: AvatarNodeImageOverride? + if strongSelf.context.account.peerId == peer.id { + imageOverride = .savedMessagesIcon + } else if peer.isDeleted { + imageOverride = .deletedIcon + } else { + imageOverride = nil + } + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil + } + + if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { + return + } + + strongSelf.reportIrrelvantGeoNotice = peerReportNotice + strongSelf.hasScheduledMessages = hasScheduledMessages + + var upgradedToPeerId: PeerId? + if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { + upgradedToPeerId = migrationReference.peerId + } + var wasGroupChannel: Bool? + if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + wasGroupChannel = true + } else { + wasGroupChannel = false + } + } + var isGroupChannel: Bool? + if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false + } + } + let firstTime = strongSelf.peerView == nil + strongSelf.peerView = peerView + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + strongSelf.chatAdditionalDataDisposable.set(disposable) + } else { + strongSelf.chatAdditionalDataDisposable.set(nil) + } + } + if strongSelf.isNodeLoaded { + strongSelf.chatDisplayNode.peerView = peerView + } + var peerIsMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if case .broadcast = peer.info { + peerDiscussionId = cachedData.linkedDiscussionPeerId + } else { + peerGeoLocation = cachedData.peerGeoLocation + } + } + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + if let peer = peerView.peers[peerView.peerId] { + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var canReportIrrelevantLocation = true + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { + canReportIrrelevantLocation = false + } + if let peerReportNotice = peerReportNotice, peerReportNotice { + canReportIrrelevantLocation = false + } + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } + + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) + } + + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible + } + + if firstTime && isNotAccessible { + strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + if let peer = peerView.peers[peerView.peerId] { + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true + } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true + } + } + } + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + var animated = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { + animated = true + } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { + if peer.participationStatus != updated.participationStatus { + animated = true + } + } + + var didDisplayActionsPanel = false + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + didDisplayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + didDisplayActionsPanel = true + } + } + } + + var displayActionsPanel = false + if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } + } + } + + if displayActionsPanel != didDisplayActionsPanel { + animated = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId { + strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { + return $0.updatedPeer { _ in + return renderedPeer + }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) + }) + if !strongSelf.didSetChatLocationInfoReady { + strongSelf.didSetChatLocationInfoReady = true + strongSelf._chatLocationInfoReady.set(.single(true)) + } + strongSelf.updateReminderActivity() + if let upgradedToPeerId = upgradedToPeerId { + if let navigationController = strongSelf.effectiveNavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { + viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) + navigationController.setViewControllers(viewControllers, animated: false) } } } } - - self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in - if let strongSelf = self { - if let peer = peerViewMainPeer(peerView) { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) - let imageOverride: AvatarNodeImageOverride? - if strongSelf.context.account.peerId == peer.id { - imageOverride = .savedMessagesIcon - } else if peer.isDeleted { - imageOverride = .deletedIcon - } else { - imageOverride = nil - } - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil - } - - if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { - return - } - - strongSelf.reportIrrelvantGeoNotice = peerReportNotice - strongSelf.hasScheduledMessages = hasScheduledMessages - - var upgradedToPeerId: PeerId? - if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { - upgradedToPeerId = migrationReference.peerId - } - var wasGroupChannel: Bool? - if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - wasGroupChannel = true - } else { - wasGroupChannel = false - } - } - var isGroupChannel: Bool? - if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - isGroupChannel = true - } else { - isGroupChannel = false - } - } - let firstTime = strongSelf.peerView == nil - strongSelf.peerView = peerView - if wasGroupChannel != isGroupChannel { - if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let disposable = DisposableSet() - disposable.add(recentDisposable) - disposable.add(adminsDisposable) - strongSelf.chatAdditionalDataDisposable.set(disposable) - } else { - strongSelf.chatAdditionalDataDisposable.set(nil) - } - } - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.peerView = peerView - } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if case .broadcast = peer.info { - peerDiscussionId = cachedData.linkedDiscussionPeerId - } else { - peerGeoLocation = cachedData.peerGeoLocation - } - } - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - if let peer = peerView.peers[peerView.peerId] { - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - if let peerReportNotice = peerReportNotice, peerReportNotice { - canReportIrrelevantLocation = false - } - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } - - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer - } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - if let peer = peerView.peers[peerView.peerId] { - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true - } - } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { - animated = true - } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true - } - } - } - - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } - } - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) - } - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) - }) - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - strongSelf.updateReminderActivity() - if let upgradedToPeerId = upgradedToPeerId { - if let navigationController = strongSelf.effectiveNavigationController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { - viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) - navigationController.setViewControllers(viewControllers, animated: false) - } - } - } - } - })) - } + })) + } } self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() @@ -2926,7 +2995,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } override public func loadDisplayNode() { - self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) + self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in guard let strongSelf = self else { @@ -2973,6 +3042,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G pinnedMessageId = cachedData.pinnedMessageId } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { } + + if case let .replyThread(messageId, _) = strongSelf.chatLocation { + pinnedMessageId = messageId + } + var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { if let cachedDataMessages = combinedInitialData.cachedDataMessages { @@ -3071,21 +3145,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) let hasPendingMessages: Signal - if case let .peer(peerId) = self.chatLocation { - hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages - |> mapToSignal { peerIds -> Signal in - let value = peerIds.contains(peerId) - if value { - return .single(true) - } else { - return .single(false) - |> delay(0.1, queue: .mainQueue()) - } + let chatLocationPeerId = self.chatLocation.peerId + hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages + |> mapToSignal { peerIds -> Signal in + let value = peerIds.contains(chatLocationPeerId) + if value { + return .single(true) + } else { + return .single(false) + |> delay(0.1, queue: .mainQueue()) } - |> distinctUntilChanged - } else { - hasPendingMessages = .single(false) } + |> distinctUntilChanged self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages in if let strongSelf = self { @@ -3115,6 +3186,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = cachedData as? CachedSecretChatData { } + if case let .replyThread(messageId, _) = strongSelf.chatLocation { + pinnedMessageId = messageId + } + var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { pinnedMessage = messages?[pinnedMessageId] @@ -3315,7 +3390,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatDisplayNode.sendMessages = { [weak self] messages, silentPosting, scheduleTime, isAnyMessageTextPartitioned in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId strongSelf.commitPurposefulAction() if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode { @@ -3381,7 +3457,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return } - if case .peer = strongSelf.chatLocation, let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { + if let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in return transaction.getMessage(messageId) } |> deliverOnMainQueue).start(next: { message in @@ -3895,15 +3971,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateItemNodesSearchTextHighlightStates() if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer: + case .peer, .replyThread: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) - /*case .group: - strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } } }, openCalendarSearch: { [weak self] in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId strongSelf.chatDisplayNode.dismissInput() let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in @@ -3959,7 +4034,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openPeerInfo: { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) }, togglePeerNotifications: { [weak self] in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start() } }, sendContextResult: { [weak self] results, result, node, rect in @@ -4734,150 +4810,160 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current) + }, viewReplies: { [weak self] sourceMessageId, replyThreadResult in + guard let strongSelf = self else { + return + } + + if let navigationController = strongSelf.effectiveNavigationController { + let subject: ChatControllerSubject? = nil// sourceMessageId.flatMap(ChatControllerSubject.message) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadResult.messageId, maxReadMessageId: replyThreadResult.maxReadMessageId), subject: subject, keepStack: .always)) + } }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get())) - switch self.chatLocation { - case let .peer(peerId): - if let subject = self.subject, case .scheduledMessages = subject { - } else { - let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) - let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) - self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) - |> deliverOnMainQueue).start(next: { [weak self] views in - if let strongSelf = self { - var unreadCount: Int32 = 0 - var totalChatCount: Int32 = 0 - - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { - if let count = view.count(for: .peer(peerId)) { - unreadCount = count - } - if let (_, state) = view.total() { - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) - totalChatCount = count - } - } - - strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount - - if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { - var globalRemainingUnreadChatCount = totalChatCount - if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { - if case .messages = inAppSettings.totalUnreadCountDisplayCategory { - globalRemainingUnreadChatCount -= unreadCount - } else { - globalRemainingUnreadChatCount -= 1 - } - } - - if globalRemainingUnreadChatCount > 0 { - strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" - } else { - strongSelf.navigationItem.badge = "" - } - } - } - }) - - self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in - if let strongSelf = self { - if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 - } else { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = count - } - } - }) - - let postbox = self.context.account.postbox - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) - self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in - var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] - previousPeerCache.with { dict -> Void in - for (peerId, activity) in activities { - if let peer = dict[peerId] { - cachedResult.append((peer, activity)) - } else { - foundAllPeers = false - break - } - } - } - if foundAllPeers { - return .single(cachedResult) - } else { - return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] - for (peerId, activity) in activities { - if let peer = transaction.getPeer(peerId) { - result.append((peer, activity)) - peerCache[peerId] = peer - } - } - let _ = previousPeerCache.swap(peerCache) - return result - } - } - } - |> deliverOnMainQueue).start(next: { [weak self] activities in - if let strongSelf = self { - strongSelf.chatTitleView?.inputActivities = (peerId, activities) - } - }) - } - - self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] namespace in + do { + let peerId = self.chatLocation.peerId + if let subject = self.subject, case .scheduledMessages = subject { + } else if case .replyThread = self.chatLocation { + } else { + let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) + let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) + self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) + |> deliverOnMainQueue).start(next: { [weak self] views in if let strongSelf = self { - let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if inAppNotificationSettings.playSounds { - serviceSoundManager.playMessageDeliveredSound() + var unreadCount: Int32 = 0 + var totalChatCount: Int32 = 0 + + let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { + if let count = view.count(for: .peer(peerId)) { + unreadCount = count + } + if let (_, state) = view.total() { + let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) + totalChatCount = count + } } - if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { - strongSelf.openScheduledMessages() + + strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount + + if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { + var globalRemainingUnreadChatCount = totalChatCount + if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { + if case .messages = inAppSettings.totalUnreadCountDisplayCategory { + globalRemainingUnreadChatCount -= unreadCount + } else { + globalRemainingUnreadChatCount -= 1 + } + } + + if globalRemainingUnreadChatCount > 0 { + strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" + } else { + strongSelf.navigationItem.badge = "" + } } } - })) + }) - self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] reason in - if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { - let text: String - let moreInfo: Bool - switch reason { - case .flood: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood - moreInfo = true - case .publicBan: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted - moreInfo = true - case .mediaRestricted: - strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) - return - case .slowmodeActive: - text = strongSelf.presentationData.strings.Chat_SlowmodeSendError - moreInfo = false - case .tooMuchScheduled: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled - moreInfo = false - } - let actions: [TextAlertAction] - if moreInfo { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { - self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in + if let strongSelf = self { + if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { + strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 } else { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + strongSelf.chatDisplayNode.navigateButtons.mentionCount = count } - let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) - strongSelf.currentFailedMessagesAlertController = controller - strongSelf.present(controller, in: .window(.root)) } - })) + }) + + let postbox = self.context.account.postbox + let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) + |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in + var foundAllPeers = true + var cachedResult: [(Peer, PeerInputActivity)] = [] + previousPeerCache.with { dict -> Void in + for (peerId, activity) in activities { + if let peer = dict[peerId] { + cachedResult.append((peer, activity)) + } else { + foundAllPeers = false + break + } + } + } + if foundAllPeers { + return .single(cachedResult) + } else { + return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in + var result: [(Peer, PeerInputActivity)] = [] + var peerCache: [PeerId: Peer] = [:] + for (peerId, activity) in activities { + if let peer = transaction.getPeer(peerId) { + result.append((peer, activity)) + peerCache[peerId] = peer + } + } + let _ = previousPeerCache.swap(peerCache) + return result + } + } + } + |> deliverOnMainQueue).start(next: { [weak self] activities in + if let strongSelf = self { + strongSelf.chatTitleView?.inputActivities = (peerId, activities) + } + }) + } + + self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] namespace in + if let strongSelf = self { + let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if inAppNotificationSettings.playSounds { + serviceSoundManager.playMessageDeliveredSound() + } + if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { + strongSelf.openScheduledMessages() + } + } + })) + + self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] reason in + if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { + let text: String + let moreInfo: Bool + switch reason { + case .flood: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + moreInfo = true + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + moreInfo = true + case .mediaRestricted: + strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) + return + case .slowmodeActive: + text = strongSelf.presentationData.strings.Chat_SlowmodeSendError + moreInfo = false + case .tooMuchScheduled: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled + moreInfo = false + } + let actions: [TextAlertAction] + if moreInfo { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { + self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } else { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } + let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) + strongSelf.currentFailedMessagesAlertController = controller + strongSelf.present(controller, in: .window(.root)) + } + })) } self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] in @@ -5339,10 +5425,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.validLayout = layout self.chatTitleView?.layout = layout - if self.hasScheduledMessages, let h = layout.inputHeight, h > 100.0 { - print() - } - switch self.presentationInterfaceState.mode { case .standard, .inline: break @@ -6193,10 +6275,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func editMessageMediaWithLegacySignals(_ signals: [Any]) { - guard case .peer = self.chatLocation else { - return - } - let _ = (legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals) |> deliverOnMainQueue).start(next: { [weak self] messages in self?.editMessageMediaWithMessages(messages) @@ -6399,10 +6477,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - } + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) }) } }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in @@ -6612,7 +6688,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in + strongSelf.presentTimerPicker(style: .media, completion: { time in done(time) }) } @@ -7104,7 +7180,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func presentPollCreation(isQuiz: Bool? = nil) { - if case .peer = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.peer { + if let peer = self.presentationInterfaceState.renderedPeer?.peer { self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in guard let strongSelf = self else { return @@ -7170,7 +7246,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { + var defaultReplyMessageId: MessageId? + switch self.chatLocation { + case .peer: + break + case let .replyThread(messageId, _): + defaultReplyMessageId = messageId + } + return messages.map { message in + var message = message + + if let defaultReplyMessageId = defaultReplyMessageId { + switch message { + case let .message(text, attributes, mediaReference, replyToMessageId, localGroupingKey): + if replyToMessageId == nil { + message = .message(text: text, attributes: attributes, mediaReference: mediaReference, replyToMessageId: defaultReplyMessageId, localGroupingKey: localGroupingKey) + } + case .forward: + break + } + } + if silentPosting || scheduleTime != nil { return message.withUpdatedAttributes { attributes in var attributes = attributes @@ -7196,8 +7293,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) { - guard case let .peer(peerId) = self.chatLocation else { - return + let peerId: PeerId + switch self.chatLocation { + case let .peer(peerIdValue): + peerId = peerIdValue + case let .replyThread(messageId, _): + peerId = messageId.peerId } if commit || !self.presentationInterfaceState.isScheduledMessages { @@ -7221,23 +7322,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil) { - if case .peer = self.chatLocation { - self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) - |> deliverOnMainQueue).start(next: { [weak self] messages in - if let strongSelf = self { - let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) - let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } - }) - } - }) - strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) - } - })) - } + self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) + |> deliverOnMainQueue).start(next: { [weak self] messages in + if let strongSelf = self { + let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }) + strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) + } + })) } private func displayPasteMenu(_ images: [UIImage]) { @@ -7315,9 +7414,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false) { - guard case let .peer(peerId) = self.chatLocation else { - return - } + let peerId = self.chatLocation.peerId if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, hideVia: hideVia), canSendMessagesToChat(self.presentationInterfaceState) { let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId @@ -7397,9 +7494,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func requestVideoRecorder() { - guard case let .peer(peerId) = self.chatLocation else { - return - } + let peerId = self.chatLocation.peerId if self.videoRecorderValue == nil { if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() { @@ -7622,19 +7717,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } switch search.domain { case .everything: - switch self.chatLocation { - case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: nil, tags: nil, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) case .members: derivedSearchState = nil case let .member(peer): - switch self.chatLocation { - case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - /*case .group: - derivedSearchState = nil*/ - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: peer.id, tags: nil, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) } } @@ -7705,10 +7792,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer: + case .peer, .replyThread: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) - /*case .group: - strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } strongSelf.updateItemNodesSearchTextHighlightStates() @@ -7842,7 +7927,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .upperBound: searchLocation = .index(MessageIndex.upperBound(peerId: peerId)) } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -7934,7 +8019,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.historyNavigationStack.add(fromIndex) } self.loadingMessage.set(true) - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal in switch historyView { @@ -8122,60 +8207,58 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else { if let peerId = peerId { - switch self.chatLocation { - case let .peer(selfPeerId): - switch navigation { - case .info, .default: - let peerSignal: Signal - if let fromMessage = fromMessage { - peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) - } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) - } - self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, let peer = peer { - var mode: PeerInfoControllerMode = .generic - if let _ = fromMessage { - mode = .group(selfPeerId) - } - var expandAvatar = expandAvatar - if peer.smallProfileImage == nil { - expandAvatar = false - } - if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { - expandAvatar = false - } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { - strongSelf.effectiveNavigationController?.pushViewController(infoController) - } + do { + let selfPeerId = self.chatLocation.peerId + switch navigation { + case .info, .default: + let peerSignal: Signal + if let fromMessage = fromMessage { + peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) + } else { + peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + } + self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer = peer { + var mode: PeerInfoControllerMode = .generic + if let _ = fromMessage { + mode = .group(selfPeerId) } - })) - case let .chat(textInputState, subject, peekData): - if let textInputState = textInputState { - let _ = (self.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedComposeInputState(textInputState) - } else { - return ChatInterfaceState().withUpdatedComposeInputState(textInputState) - } - }) - }) - |> deliverOnMainQueue).start(completed: { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) + var expandAvatar = expandAvatar + if peer.smallProfileImage == nil { + expandAvatar = false + } + if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { + expandAvatar = false + } + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { + strongSelf.effectiveNavigationController?.pushViewController(infoController) + } + } + })) + case let .chat(textInputState, subject, peekData): + if let textInputState = textInputState { + let _ = (self.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedComposeInputState(textInputState) + } else { + return ChatInterfaceState().withUpdatedComposeInputState(textInputState) } }) - } else { - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) - } - case let .withBotStartPayload(botStart): - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) - default: - break - } - /*case .group: - (self.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), messageId: fromMessage?.id, botStart: nil))*/ + }) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) + } + }) + } else { + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) + } + case let .withBotStartPayload(botStart): + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) + default: + break + } } } else { switch navigation { @@ -8798,81 +8881,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - @available(iOSApplicationExtension 9.0, iOS 9.0, *) - override public var previewActionItems: [UIPreviewActionItem] { - struct PreviewActionsData { - let notificationSettings: PeerNotificationSettings? - let peer: Peer? - } - let chatLocation = self.chatLocation - let data = Atomic(value: nil) - let semaphore = DispatchSemaphore(value: 0) - let _ = self.context.account.postbox.transaction({ transaction -> Void in - switch chatLocation { - case let .peer(peerId): - let _ = data.swap(PreviewActionsData(notificationSettings: transaction.getPeerNotificationSettings(peerId), peer: transaction.getPeer(peerId))) - /*case .group: - let _ = data.swap(PreviewActionsData(notificationSettings: nil, peer: nil))*/ - } - semaphore.signal() - }).start() - semaphore.wait() - - return data.with { [weak self] data -> [UIPreviewActionItem] in - var items: [UIPreviewActionItem] = [] - if let data = data, let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - - switch strongSelf.peekActions { - case .standard: - if let peer = data.peer, peer.id != strongSelf.context.account.peerId { - if let _ = data.peer as? TelegramUser { - items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: strongSelf.transformEnqueueMessages([.message(text: "👍", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)])).start() - } - })) - } - - if let notificationSettings = data.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - items.append(UIPreviewAction(title: presentationData.strings.Conversation_Unmute, style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peer.id).start() - } - })) - } else { - let muteInterval: Int32 - if let _ = data.peer as? TelegramChannel { - muteInterval = Int32.max - } else { - muteInterval = 1 * 60 * 60 - } - let title: String - if muteInterval == Int32.max { - title = presentationData.strings.Conversation_Mute - } else { - title = muteForIntervalString(strings: presentationData.strings, value: muteInterval) - } - - items.append(UIPreviewAction(title: title, style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = updatePeerMuteSetting(account: strongSelf.context.account, peerId: peer.id, muteInterval: muteInterval).start() - } - })) - } - } - } - case let .remove(action): - items.append(UIPreviewAction(title: presentationData.strings.Common_Delete, style: .destructive, handler: { _, _ in - action() - })) - } - } - return items - } - } - private func debugStreamSingleVideo(_ id: MessageId) { let gallery = GalleryController(context: self.context, source: .peerMessagesAtId(id), streamSingleVideo: true, replaceRootController: { [weak self] controller, ready in if let strongSelf = self { @@ -8904,7 +8912,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set, options: ChatAvailableMessageActionOptions) { - if case let .peer(peerId) = self.chatLocation { + let peerId = self.chatLocation.peerId + do { self.navigationActionDisposable.set((fetchChannelParticipant(account: self.context.account, peerId: peerId, participantId: author.id) |> deliverOnMainQueue).start(next: { [weak self] participant in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 5b2c15dd75..24780e77cd 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -111,6 +111,7 @@ public final class ChatControllerInteraction { let animateDiceSuccess: () -> Void let greetingStickerNode: () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)? let openPeerContextMenu: (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void + let openMessageReplies: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -128,7 +129,78 @@ public final class ChatControllerInteraction { var searchTextHighightState: (String, [MessageIndex])? var seenOneTimeAnimatedMedia = Set() - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId, Bool) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { + init( + openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, + openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, + openPeerMention: @escaping (String) -> Void, + openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, + openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, + navigateToMessage: @escaping (MessageId, MessageId) -> Void, + tapMessage: ((Message) -> Void)?, + clickThroughMessage: @escaping () -> Void, + toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, + sendCurrentMessage: @escaping (Bool) -> Void, + sendMessage: @escaping (String) -> Void, + sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, + sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, + sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, + requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, + requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, + activateSwitchInline: @escaping (PeerId?, String) -> Void, + openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, + shareCurrentLocation: @escaping () -> Void, + shareAccountContact: @escaping () -> Void, + sendBotCommand: @escaping (MessageId?, String) -> Void, + openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, + openWallpaper: @escaping (Message) -> Void, + openTheme: @escaping (Message) -> Void, + openHashtag: @escaping (String?, String) -> Void, + updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, + updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, + openMessageShareMenu: @escaping (MessageId) -> Void, + presentController: @escaping (ViewController, Any?) -> Void, + navigationController: @escaping () -> NavigationController?, + chatControllerNode: @escaping () -> ASDisplayNode?, + reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, + presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, + callPeer: @escaping (PeerId, Bool) -> Void, + longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, + openCheckoutOrReceipt: @escaping (MessageId) -> Void, + openSearch: @escaping () -> Void, + setupReply: @escaping (MessageId) -> Void, + canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, + navigateToFirstDateMessage: @escaping(Int32) ->Void, + requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, + addContact: @escaping (String) -> Void, + rateCall: @escaping (Message, CallId, Bool) -> Void, + requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, + requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, + openAppStorePage: @escaping () -> Void, + displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, + seekToTimecode: @escaping (Message, Double, Bool) -> Void, + scheduleCurrentMessage: @escaping () -> Void, + sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, + editScheduledMessagesTime: @escaping ([MessageId]) -> Void, + performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, + updateMessageLike: @escaping (MessageId, Bool) -> Void, + openMessageReactions: @escaping (MessageId) -> Void, + displaySwipeToReplyHint: @escaping () -> Void, + dismissReplyMarkupMessage: @escaping (Message) -> Void, + openMessagePollResults: @escaping (MessageId, Data) -> Void, + openPollCreation: @escaping (Bool?) -> Void, + displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, + displayPsa: @escaping (String, ASDisplayNode) -> Void, + displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, + animateDiceSuccess: @escaping () -> Void, + greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, + openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, + openMessageReplies: @escaping (MessageId) -> Void, + requestMessageUpdate: @escaping (MessageId) -> Void, + cancelInteractiveKeyboardGestures: @escaping () -> Void, + automaticMediaDownloadSettings: MediaAutoDownloadSettings, + pollActionState: ChatInterfacePollActionState, + stickerSettings: ChatInterfaceStickerSettings + ) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -193,6 +265,7 @@ public final class ChatControllerInteraction { self.animateDiceSuccess = animateDiceSuccess self.greetingStickerNode = greetingStickerNode self.openPeerContextMenu = openPeerContextMenu + self.openMessageReplies = openMessageReplies self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures @@ -241,6 +314,7 @@ public final class ChatControllerInteraction { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 7f61599a74..1eec7a78e5 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -414,11 +414,29 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var derivedLayoutState: ChatControllerNodeDerivedLayoutState? - private var isLoading: Bool = false { - didSet { - if self.isLoading != oldValue { - if self.isLoading { - self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) + private var isLoadingValue: Bool = false + private func updateIsLoading(isLoading: Bool, animated: Bool) { + if isLoading != self.isLoadingValue { + self.isLoadingValue = isLoading + if isLoading { + self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) + self.loadingNode.layer.removeAllAnimations() + self.loadingNode.alpha = 1.0 + if animated { + self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + } else { + self.loadingNode.alpha = 0.0 + if animated { + self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) + self.loadingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in + if let strongSelf = self { + strongSelf.loadingNode.layer.removeAllAnimations() + if completed { + strongSelf.loadingNode.removeFromSupernode() + } + } + }) } else { self.loadingNode.removeFromSupernode() } @@ -441,7 +459,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } private var didProcessExperimentalEmbedUrl: String? - init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { + init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { self.context = context self.chatLocation = chatLocation self.controllerInteraction = controllerInteraction @@ -458,7 +476,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() - self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) self.historyNode.rotated = true self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) @@ -521,9 +539,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in if let strongSelf = self { if case .loading = loadState { - strongSelf.isLoading = true + strongSelf.updateIsLoading(isLoading: true, animated: animated) } else { - strongSelf.isLoading = false + strongSelf.updateIsLoading(isLoading: false, animated: animated) } var isEmpty = false @@ -2137,10 +2155,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func loadInputPanels(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) { if self.inputMediaNode == nil { - var peerId: PeerId? - if case let .peer(id) = self.chatPresentationInterfaceState.chatLocation { - peerId = id - } + let peerId: PeerId? = self.chatPresentationInterfaceState.chatLocation.peerId let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in @@ -2694,7 +2709,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText let trimmedInputText = effectiveInputText.string.trimmingCharacters(in: .whitespacesAndNewlines) - if case let .peer(peerId) = effectivePresentationInterfaceState.chatLocation, peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { + let peerId = effectivePresentationInterfaceState.chatLocation.peerId + if peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { messages.append(.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: trimmedInputText)), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)) } else { let inputText = convertMarkdownToAttributes(effectiveInputText) @@ -2746,9 +2762,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - if case .peer = self.chatLocation { - self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) - } + self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) } } } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 6ce1a049f6..64de0c4ed3 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -38,7 +38,17 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod self.currentStrings = interfaceState.strings let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - self.textNode.attributedText = NSAttributedString(string: interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText) + + let text: String + switch interfaceState.chatLocation { + case .peer: + text = interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder + case .replyThread: + //TODO:localize + text = "No comments here yet" + } + + self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText) } let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0) @@ -639,7 +649,9 @@ final class ChatEmptyNode: ASDisplayNode { } let contentType: ChatEmptyNodeContentType - if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { + if case .replyThread = interfaceState.chatLocation { + contentType = .regular + } else if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { if peer.id == self.account.peerId { contentType = .cloud } else if let _ = peer as? TelegramSecretChat { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index c028ffe8dc..ec41ebd1c7 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -7,6 +7,7 @@ import Emoji import AccountContext import TelegramPresentationData + func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia]) -> [ChatHistoryEntry] { if historyAppearsCleared { return [] diff --git a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift deleted file mode 100644 index f66ed95d0d..0000000000 --- a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift +++ /dev/null @@ -1,513 +0,0 @@ -import Foundation -import UIKit -import Postbox -import SwiftSignalKit -import Display -import AsyncDisplayKit -import TelegramCore -import SyncCore -import TelegramPresentationData -import TelegramUIPreferences -import AccountContext - -private class ChatGridLiveSelectorRecognizer: UIPanGestureRecognizer { - private let selectionGestureActivationThreshold: CGFloat = 2.0 - private let selectionGestureVerticalFailureThreshold: CGFloat = 5.0 - - var validatedGesture: Bool? = nil - var firstLocation: CGPoint = CGPoint() - - var shouldBegin: (() -> Bool)? - - override init(target: Any?, action: Selector?) { - super.init(target: target, action: action) - - self.maximumNumberOfTouches = 1 - } - - override func reset() { - super.reset() - - self.validatedGesture = nil - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - if let shouldBegin = self.shouldBegin, !shouldBegin() { - self.state = .failed - } else { - let touch = touches.first! - self.firstLocation = touch.location(in: self.view) - } - } - - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - let location = touches.first!.location(in: self.view) - let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y) - - if self.validatedGesture == nil { - if (fabs(translation.y) >= selectionGestureVerticalFailureThreshold) { - self.validatedGesture = false - } - else if (fabs(translation.x) >= selectionGestureActivationThreshold) { - self.validatedGesture = true - } - } - - if let validatedGesture = self.validatedGesture { - if validatedGesture { - super.touchesMoved(touches, with: event) - } - } - } -} - -struct ChatHistoryGridViewTransition { - let historyView: ChatHistoryView - let topOffsetWithinMonth: Int - let deleteItems: [Int] - let insertItems: [GridNodeInsertItem] - let updateItems: [GridNodeUpdateItem] - let scrollToItem: GridNodeScrollToItem? - let stationaryItems: GridNodeStationaryItems -} - -private func mappedInsertEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionInsertEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeInsertItem] { - return entries.map { entry -> GridNodeInsertItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _, _): - return GridNodeInsertItem(index: entry.index, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction), previousIndex: entry.previousIndex) - case .MessageGroupEntry: - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - case .UnreadEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - case .ChatInfoEntry, .SearchEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - } - } -} - -private func mappedUpdateEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionUpdateEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeUpdateItem] { - return entries.map { entry -> GridNodeUpdateItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _, _): - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction)) - case .MessageGroupEntry: - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - case .UnreadEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - case .ChatInfoEntry, .SearchEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - } - } -} - -private func mappedChatHistoryViewListTransition(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, transition: ChatHistoryViewTransition, from: ChatHistoryView?, presentationData: ChatPresentationData) -> ChatHistoryGridViewTransition { - var mappedScrollToItem: GridNodeScrollToItem? - if let scrollToItem = transition.scrollToItem { - let mappedPosition: GridNodeScrollToItemPosition - switch scrollToItem.position { - case .top: - mappedPosition = .top(0.0) - case .center: - mappedPosition = .center(0.0) - case .bottom: - mappedPosition = .bottom(0.0) - case .visible: - mappedPosition = .bottom(0.0) - } - let scrollTransition: ContainedViewLayoutTransition - if scrollToItem.animated { - switch scrollToItem.curve { - case .Default: - scrollTransition = .animated(duration: 0.3, curve: .easeInOut) - case let .Spring(duration): - scrollTransition = .animated(duration: duration, curve: .spring) - } - } else { - scrollTransition = .immediate - } - let directionHint: GridNodePreviousItemsTransitionDirectionHint - switch scrollToItem.directionHint { - case .Up: - directionHint = .up - case .Down: - directionHint = .down - } - mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition, transition: scrollTransition, directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) - } - - var stationaryItems: GridNodeStationaryItems = .none - if let previousView = from { - if let stationaryRange = transition.stationaryItemRange { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - if i >= stationaryRange.0 && i <= stationaryRange.1 { - fromStableIds.insert(previousView.filteredEntries[i].stableId) - } - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } else { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - fromStableIds.insert(previousView.filteredEntries[i].stableId) - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } - } - - var topOffsetWithinMonth: Int = 0 - if let lastEntry = transition.historyView.filteredEntries.last { - switch lastEntry { - case let .MessageEntry(_, _, _, monthLocation, _, _): - if let monthLocation = monthLocation { - topOffsetWithinMonth = Int(monthLocation.indexInMonth) - } - default: - break - } - } - - return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), updateItems: mappedUpdateEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) -} - -private func gridNodeLayoutForContainerLayout(size: CGSize) -> GridNodeLayoutType { - let side = floorToScreenPixels((size.width - 3.0) / 4.0) - return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) -} - -public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { - private let context: AccountContext - private let peerId: PeerId - private let messageId: MessageId? - private let tagMask: MessageTags? - - private var historyView: ChatHistoryView? - - private let historyDisposable = MetaDisposable() - - private let messageViewQueue = Queue() - - private var dequeuedInitialTransitionOnLayout = false - private var enqueuedHistoryViewTransition: (ChatHistoryGridViewTransition, () -> Void)? - var layoutActionOnViewTransition: ((ChatHistoryGridViewTransition) -> (ChatHistoryGridViewTransition, ListViewUpdateSizeAndInsets?))? - - public let historyState = ValuePromise() - private var currentHistoryState: ChatHistoryNodeHistoryState? - - public var preloadPages: Bool = true { - didSet { - if self.preloadPages != oldValue { - - } - } - } - - private let _chatHistoryLocation = ValuePromise(ignoreRepeated: true) - private var chatHistoryLocation: Signal { - return self._chatHistoryLocation.get() - } - - private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() - - private var presentationData: PresentationData - private let chatPresentationDataPromise = Promise() - - public private(set) var loadState: ChatHistoryNodeLoadState? - private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? - private let controllerInteraction: ChatControllerInteraction - - public init(context: AccountContext, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) { - self.context = context - self.peerId = peerId - self.messageId = messageId - self.tagMask = tagMask - self.controllerInteraction = controllerInteraction - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - super.init() - - self.chatPresentationDataPromise.set(context.sharedContext.presentationData - |> map { presentationData in - return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) - }) - - self.floatingSections = true - - let messageViewQueue = self.messageViewQueue - - let historyViewUpdate = self.chatHistoryLocation - |> distinctUntilChanged - |> mapToSignal { location in - return chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), account: context.account, chatLocation: .peer(peerId), scheduled: false, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) - } - - let previousView = Atomic(value: nil) - - let historyViewTransition = combineLatest(queue: messageViewQueue, historyViewUpdate, self.chatPresentationDataPromise.get()) - |> mapToQueue { [weak self] update, chatPresentationData -> Signal in - switch update { - case .Loading: - Queue.mainQueue().async { [weak self] in - if let strongSelf = self { - let loadState: ChatHistoryNodeLoadState = .loading - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loading - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - } - } - return .complete() - case let .HistoryView(view, type, scrollPosition, flashIndicators, _, _, id): - let reason: ChatHistoryViewTransitionReason - switch type { - case let .Initial(fadeIn): - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: fadeIn) - case let .Generic(genericType): - switch genericType { - case .InitialUnread, .Initial: - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: false) - case .Generic: - reason = ChatHistoryViewTransitionReason.InteractiveChanges - case .UpdateVisible: - reason = ChatHistoryViewTransitionReason.Reload - case .FillHole: - reason = ChatHistoryViewTransitionReason.Reload - } - } - - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: false) - let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: .peer(peerId), view: view, includeUnreadEntry: false, includeEmptyEntry: false, includeChatInfoEntry: false, includeSearchEntry: false, reverse: false, groupMessages: false, selectedMessages: nil, presentationData: chatPresentationData, historyAppearsCleared: false, associatedData: associatedData, updatingMedia: [:]), associatedData: associatedData, lastHeaderId: 0, id: id) - let previous = previousView.swap(processedView) - - let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil, flashIndicators: flashIndicators, updatedMessageSelection: false) - let mappedTransition = mappedChatHistoryViewListTransition(context: context, peerId: peerId, controllerInteraction: controllerInteraction, transition: rawTransition, from: previous, presentationData: chatPresentationData) - return .single(mappedTransition) - } - } - - let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in - if let strongSelf = self { - return strongSelf.enqueueHistoryViewTransition(transition) - } - return .complete() - } - - self.historyDisposable.set(appliedTransition.start()) - - if let messageId = messageId { - self._chatHistoryLocation.set(ChatHistoryLocation.InitialSearch(location: .id(messageId), count: 100)) - } else { - self._chatHistoryLocation.set(ChatHistoryLocation.Initial(count: 100)) - } - - self.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self, let historyView = strongSelf.historyView, let top = visibleItems.top, let bottom = visibleItems.bottom, let visibleTop = visibleItems.topVisible, let visibleBottom = visibleItems.bottomVisible { - if top.0 < 5 && historyView.originalView.laterId != nil { - let lastEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleTop.0] - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: 100)) - } else if bottom.0 >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - let firstEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleBottom.0] - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: 100)) - } - } - } - - let selectorRecogizner = ChatGridLiveSelectorRecognizer(target: self, action: #selector(self.panGesture(_:))) - selectorRecogizner.shouldBegin = { [weak controllerInteraction] in - return controllerInteraction?.selectionState != nil - } - self.view.addGestureRecognizer(selectorRecogizner) - } - - public override func didLoad() { - super.didLoad() - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.historyDisposable.dispose() - } - - public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) { - self.loadStateUpdated = f - } - - public func scrollToStartOfHistory() { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true)) - } - - public func scrollToEndOfHistory() { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true)) - } - - public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, scrollPosition: ListViewScrollPosition = .center(.bottom)) { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: .center(.bottom), animated: true)) - } - - public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { - if let historyView = self.historyView { - for case let .MessageEntry(message, _, _, _, _, _) in historyView.filteredEntries where message.id == id { - return message - } - } - return nil - } - - private func enqueueHistoryViewTransition(_ transition: ChatHistoryGridViewTransition) -> Signal { - return Signal { [weak self] subscriber in - if let strongSelf = self { - if let _ = strongSelf.enqueuedHistoryViewTransition { - preconditionFailure() - } - - strongSelf.enqueuedHistoryViewTransition = (transition, { - subscriber.putCompletion() - }) - - if strongSelf.isNodeLoaded { - strongSelf.dequeueHistoryViewTransition() - } else { - let loadState: ChatHistoryNodeLoadState - if transition.historyView.filteredEntries.isEmpty { - loadState = .empty - } else { - loadState = .messages - } - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - } - } else { - subscriber.putCompletion() - } - - return EmptyDisposable - } |> runOn(Queue.mainQueue()) - } - - private func dequeueHistoryViewTransition() { - if let (transition, completion) = self.enqueuedHistoryViewTransition { - self.enqueuedHistoryViewTransition = nil - - let completion: (GridNodeDisplayedItemRange) -> Void = { [weak self] visibleRange in - if let strongSelf = self { - strongSelf.historyView = transition.historyView - - let loadState: ChatHistoryNodeLoadState - if let historyView = strongSelf.historyView { - if historyView.filteredEntries.isEmpty { - loadState = .empty - } else { - loadState = .messages - } - } else { - loadState = .loading - } - - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - - completion() - } - } - - if let layoutActionOnViewTransition = self.layoutActionOnViewTransition { - self.layoutActionOnViewTransition = nil - let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition) - - var updateLayout: GridNodeUpdateLayout? - if let updateSizeAndInsets = updateSizeAndInsets { - updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: CGSize(width: 200.0, height: 200.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate) - } - - self.transaction(GridNodeTransaction(deleteItems: mappedTransition.deleteItems, insertItems: mappedTransition.insertItems, updateItems: mappedTransition.updateItems, scrollToItem: mappedTransition.scrollToItem, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: mappedTransition.topOffsetWithinMonth), completion: completion) - } else { - self.transaction(GridNodeTransaction(deleteItems: transition.deleteItems, insertItems: transition.insertItems, updateItems: transition.updateItems, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.topOffsetWithinMonth, synchronousLoads: true), completion: completion) - } - } - } - - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(size: updateSizeAndInsets.size)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - if !self.dequeuedInitialTransitionOnLayout { - self.dequeuedInitialTransitionOnLayout = true - self.dequeueHistoryViewTransition() - } - - } - - public func disconnect() { - self.historyDisposable.set(nil) - } - - private var selectionPanState: (selecting: Bool, currentMessageId: MessageId)? - - @objc private func panGesture(_ recognizer: UIGestureRecognizer) -> Void { - guard let selectionState = self.controllerInteraction.selectionState else {return} - - switch recognizer.state { - case .began: - if let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId { - self.selectionPanState = (selecting: !selectionState.selectedIds.contains(messageId), currentMessageId: messageId) - self.controllerInteraction.toggleMessagesSelection([messageId], !selectionState.selectedIds.contains(messageId)) - } - case .changed: - if let selectionPanState = self.selectionPanState, let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId, messageId != selectionPanState.currentMessageId { - self.controllerInteraction.toggleMessagesSelection([messageId], selectionPanState.selecting) - self.selectionPanState?.currentMessageId = messageId - } - case .ended, .failed, .cancelled: - self.selectionPanState = nil - case .possible: - break - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 6700ee8f57..a1ef2cf52e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -529,7 +529,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var loadedMessagesFromCachedDataDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { self.context = context self.chatLocation = chatLocation self.subject = subject @@ -584,6 +584,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let subject = subject, case .scheduledMessages = subject { scheduled = true } + var isAuxiliaryChat = scheduled + if case .replyThread = chatLocation { + isAuxiliaryChat = true + } var additionalData: [AdditionalMessageHistoryViewData] = [] if case let .peer(peerId) = chatLocation { @@ -598,16 +602,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { additionalData.append(.peerIsContact(peerId)) } } - if !scheduled { + if !isAuxiliaryChat { additionalData.append(.totalUnreadState) } + if case let .replyThread(messageId, _) = chatLocation { + additionalData.append(.message(messageId)) + } let currentViewVersion = Atomic(value: nil) let historyViewUpdate = self.chatHistoryLocationPromise.get() |> distinctUntilChanged |> mapToSignal { location in - return chatHistoryViewForLocation(location, account: context.account, chatLocation: chatLocation, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) |> beforeNext { viewUpdate in switch viewUpdate { case let .HistoryView(view, _, _, _, _, _, _): @@ -725,7 +732,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf._initialData.set(.single(combinedInitialData)) } - strongSelf._cachedPeerDataAndMessages.set(.single((nil, nil))) + let cachedData = initialData?.cachedData + let cachedDataMessages = initialData?.cachedDataMessages + + strongSelf._cachedPeerDataAndMessages.set(.single((cachedData, cachedDataMessages))) let loadState: ChatHistoryNodeLoadState = .loading if strongSelf.loadState != loadState { @@ -846,9 +856,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if apply { switch chatLocation { - case .peer: - if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - let _ = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: messageIndex).start() + case .peer, .replyThread: + if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { + context.applyMaxReadIndex(for: chatLocation, contextHolder: chatLocationContextHolder, messageIndex: messageIndex) } } } @@ -1465,12 +1475,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, animated) + strongSelf.loadStateUpdated?(loadState, animated || transition.animateIn || animateIn) } - if let range = visibleRange.loadedRange { + if let _ = visibleRange.loadedRange { if let visible = visibleRange.visibleRange { - var visibleFirstIndex = visible.firstIndex + let visibleFirstIndex = visible.firstIndex /*if !visible.firstIndexFullyVisible { visibleFirstIndex += 1 }*/ diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 75ed081bcb..bad6e9fce0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -7,8 +7,9 @@ import SwiftSignalKit import Display import AccountContext -func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { - return chatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) +func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + let chatLocationContextHolder = Atomic(value: nil) + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) |> castError(Bool.self) |> mapToSignal { update -> Signal in switch update { @@ -26,7 +27,8 @@ func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, a |> restartIfError } -func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { +func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + let account = context.account if scheduled { var first = true var chatScrollPosition: ChatHistoryViewScrollPosition? @@ -34,7 +36,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up chatScrollPosition = .index(index: index, position: position, directionHint: directionHint, animated: animated) } - return account.viewTracker.scheduledMessagesViewForLocation(chatLocation, additionalData: additionalData) + return account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) @@ -65,9 +67,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> if let tagMask = tagMask { - signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) } else { - signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(chatLocation, count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -140,9 +142,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> switch searchLocation { case let .index(index): - signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) case let .id(id): - signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -190,7 +192,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A } case let .Navigation(index, anchorIndex, count): var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) let genericType: ViewUpdateType @@ -206,7 +208,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) @@ -228,9 +230,9 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]? - ) { +) { var cachedData: CachedPeerData? - var cachedDataMessages: [MessageId: Message]? + var cachedDataMessages: [MessageId: Message] = [:] var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData] = [:] var notificationSettings: PeerNotificationSettings? @@ -253,7 +255,15 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL } case let .cachedPeerDataMessages(peerIdValue, value): if case .peer(peerIdValue) = chatLocation { - cachedDataMessages = value + if let value = value { + for (_, message) in value { + cachedDataMessages[message.id] = message + } + } + } + case let .message(_, message): + if let message = message { + cachedDataMessages[message.id] = message } case let .totalUnreadState(totalUnreadState): switch chatLocation { @@ -263,8 +273,8 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL readStateData[peerId] = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalState: totalUnreadState, notificationSettings: notificationSettings) } } - /*case .group: - break*/ + case .replyThread: + break } default: break diff --git a/submodules/TelegramUI/Sources/ChatInfo.swift b/submodules/TelegramUI/Sources/ChatInfo.swift index 0ddd82763a..126f2d43a1 100644 --- a/submodules/TelegramUI/Sources/ChatInfo.swift +++ b/submodules/TelegramUI/Sources/ChatInfo.swift @@ -5,6 +5,3 @@ import SyncCore import Display import AccountContext -func peerSharedMediaControllerImpl(context: AccountContext, peerId: PeerId) -> ViewController? { - return PeerMediaCollectionController(context: context, peerId: peerId) -} diff --git a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift index 9f8775fe8b..6d54ce28f0 100644 --- a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift @@ -163,6 +163,8 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode { } else { updatedButtons = [] } + case .replyThread: + updatedButtons = [] } var buttonsUpdated = false diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 3ca84785e2..b08c266d6e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -155,8 +155,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } else { canReply = true } - /*case .group: - break*/ + case .replyThread: + canReply = true } return canReply } @@ -302,7 +302,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: canPin = false } else if messages[0].flags.intersection([.Failed, .Unsent]).isEmpty { switch chatPresentationInterfaceState.chatLocation { - case .peer: + case .peer, .replyThread: if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { if !isAction { canPin = channel.hasPermission(.pinMessages) @@ -574,6 +574,60 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } + var threadId: Int64? + if case .peer = chatPresentationInterfaceState.chatLocation { + if let value = messages[0].threadId { + threadId = value + } else { + for attribute in messages[0].attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute, attribute.count > 0 { + threadId = makeMessageThreadId(messages[0].id) + } + } + } + } + + if let threadId = threadId { + let replyThreadId = makeThreadIdMessageId(peerId: messages[0].id.peerId, threadId: threadId) + //TODO:localize + actions.append(.action(ContextMenuActionItem(text: "View Replies", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + let foundIndex = Promise() + if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info { + foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id)) + } + c.dismiss(completion: { + if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { + if case .group = channel.info { + interfaceInteraction.viewReplies(messages[0].id, ChatReplyThreadMessage(messageId: replyThreadId, maxReadMessageId: nil)) + } else { + var cancelImpl: (() -> Void)? + let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { + cancelImpl?() + })) + controllerInteraction.presentController(statusController, nil) + + let disposable = (foundIndex.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statusController] result in + statusController?.dismiss() + + if let result = result { + interfaceInteraction.viewReplies(nil, result) + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } + } + } + }) + }))) + } + if data.canEdit { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) @@ -610,7 +664,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if data.canPin { + if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation { if chatPresentationInterfaceState.pinnedMessage?.id != messages[0].id { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor) @@ -749,94 +803,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: }))) } - if canDiscuss { - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDiscuss, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) - }, action: { c, _ in - let timestamp = messages[0].timestamp - let channelMessageId = messages[0].id - - enum DiscussMessageResult { - case message(MessageId) - case peer(PeerId) - } - - let signal = context.account.postbox.transaction { transaction -> PeerId? in - if let cachedData = transaction.getPeerCachedData(peerId: messages[0].id.peerId) as? CachedChannelData { - return cachedData.linkedDiscussionPeerId - } else { - return nil - } - } - |> mapToSignal { peerId -> Signal in - guard let peerId = peerId else { - return .single(nil) - } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp)), count: 30), id: 0), account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) - return historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in - switch historyView { - case .Loading: - return .single((nil, true)) - case let .HistoryView(view, _, _, _, _, _, _): - for entry in view.entries { - for attribute in entry.message.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - if attribute.messageId == channelMessageId { - return .single((entry.message.index, false)) - } - } - } - } - return .single((nil, false)) - } - } - |> take(until: { index in - return SignalTakeAction(passthrough: true, complete: !index.1) - }) - |> last - |> map { result -> DiscussMessageResult? in - if let index = result?.0 { - return .message(index.id) - } else { - return .peer(peerId) - } - } - } - - let foundIndex = Promise() - foundIndex.set(signal) - - c.dismiss(completion: { [weak interfaceInteraction] in - var cancelImpl: (() -> Void)? - let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { - cancelImpl?() - })) - controllerInteraction.presentController(statusController, nil) - - let disposable = (foundIndex.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] result in - statusController?.dismiss() - - if let result = result { - switch result { - case let .message(id): - interfaceInteraction?.navigateToMessage(id) - case let .peer(peerId): - interfaceInteraction?.navigateToChat(peerId) - } - } - }) - - cancelImpl = { [weak statusController] in - disposable.dispose() - statusController?.dismiss() - } - }) - }))) - } - if data.messageActions.options.contains(.report) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor) @@ -846,7 +812,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } var clearCacheAsDelete = false - if let peer = message.peers[message.id.peerId] as? TelegramChannel { + if let _ = message.peers[message.id.peerId] as? TelegramChannel { clearCacheAsDelete = true } if (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index a3773f5f6a..10f21d6ed8 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -71,6 +71,17 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState var displayInputTextPanel = false if let peer = chatPresentationInterfaceState.renderedPeer?.peer { + if peer.id.id == 708513 { + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) + } + } + if let secretChat = peer as? TelegramSecretChat { switch secretChat.embeddedState { case .handshake: @@ -109,7 +120,9 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .member: isMember = true case .left: - break + if case .replyThread = chatPresentationInterfaceState.chatLocation { + isMember = true + } } if isMember && channel.hasBannedPermission(.banSendMessages) != nil { @@ -140,13 +153,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .group: switch channel.participationStatus { case .kicked, .left: - if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { - return (currentPanel, nil) - } else { - let panel = ChatChannelSubscriberInputPanelNode() - panel.interfaceInteraction = interfaceInteraction - panel.context = context - return (panel, nil) + if !isMember { + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) + } } case .member: break diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 597862487c..233339ca6b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -64,6 +64,14 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha } func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { + if case .replyThread = presentationInterfaceState.chatLocation { + return nil + } + if case let .peer(peerId) = presentationInterfaceState.chatLocation { + if peerId.id == 708513 { + return nil + } + } if let _ = presentationInterfaceState.interfaceState.selectionState { if let currentButton = currentButton, currentButton.action == .cancelMessageSelection { return currentButton diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 5302a782b6..9f33717dce 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1009,7 +1009,7 @@ final class ChatMediaInputNode: ChatInputNode { return } - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in }, baseNavigationController: nil) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index b27c890dba..4404f27d79 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -466,8 +466,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - /*case .group: - hasAvatar = true*/ + case let .replyThread(messageId, _): + if messageId.peerId != item.context.account.peerId { + if messageId.peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true + } + } + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -558,11 +571,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -581,7 +599,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -629,7 +647,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 42c1b7952a..a151bc97ac 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -316,11 +316,16 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -566,7 +571,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) } default: break diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 2da9eb05e9..091734ed14 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -43,7 +43,7 @@ enum ChatMessageBubbleMergeStatus { enum ChatMessageBubbleRelativePosition { case None(ChatMessageBubbleMergeStatus) case BubbleNeighbour - case Neighbour + case Neighbour(Bool) } enum ChatMessageBubbleContentMosaicNeighbor { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0a03a0f7de..fe0a4a7627 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -29,54 +29,56 @@ enum InternalBubbleTapAction { case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) } -private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] { - var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = [] +private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes, Bool)] { + var result: [(Message, AnyClass, ChatMessageEntryAttributes, Bool)] = [] var skipText = false var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)? var isUnsupportedMedia = false + var isAction = false outer: for (message, itemAttributes) in item.content { for attribute in message.attributes { if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil { - result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, false)) break outer } } inner: for media in message.media { if let _ = media as? TelegramMediaImage { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, false)) } else if let file = media as? TelegramMediaFile { - var isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) + let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) if isVideo { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, false)) } else { - result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, false)) } } else if let action = media as? TelegramMediaAction { + isAction = true if case .phoneCall = action.action { - result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, false)) } else { - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, false)) } } else if let _ = media as? TelegramMediaMap { - result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, false)) } else if let _ = media as? TelegramMediaGame { skipText = true - result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, false)) break inner } else if let _ = media as? TelegramMediaInvoice { skipText = true - result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, false)) break inner } else if let _ = media as? TelegramMediaContact { - result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, false)) } else if let _ = media as? TelegramMediaExpiredContent { result.removeAll() - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, false)) return result } else if let _ = media as? TelegramMediaPoll { - result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, false)) } else if let _ = media as? TelegramMediaUnsupported { isUnsupportedMedia = true } @@ -93,7 +95,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( messageWithCaptionToAdd = (message, itemAttributes) skipText = true } else { - result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, false)) } } else { if case .group = item.content { @@ -105,32 +107,37 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( inner: for media in message.media { if let webpage = media as? TelegramMediaWebpage { if case .Loaded = webpage.content { - result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, false)) } break inner } } if isUnsupportedMedia { - result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, false)) } } if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd { - result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes)) + result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, false)) } if let additionalContent = item.additionalContent { switch additionalContent { case let .eventLogPreviousMessage(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), false)) case let .eventLogPreviousDescription(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), false)) case let .eventLogPreviousLink(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), false)) } } + let firstMessage = item.content.firstMessage + if !isAction, let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { + result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), true)) + } + return result } @@ -752,7 +759,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)), - mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), + mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), currentShareButtonNode: HighlightableButtonNode?, layoutConstants: ChatMessageItemLayoutConstants, currentItem: ChatMessageItem?, @@ -798,46 +805,54 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var hasAvatar = false var allowFullWidth = false + let chatLocationPeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - if item.message.id.peerId == item.context.account.peerId { - if let forwardInfo = item.content.firstMessage.forwardInfo { - ignoreForward = true - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) - } - } - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { - if firstMessage.forwardInfo?.author?.id == source.id { - ignoreForward = true - } - effectiveAuthor = source - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else { - effectiveAuthor = firstMessage.author - displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil - if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { - displayAuthorInfo = false + case let .peer(peerId): + chatLocationPeerId = peerId + case let .replyThread(messageId, _): + chatLocationPeerId = messageId.peerId + } + + do { + let peerId = chatLocationPeerId + if item.message.id.peerId == item.context.account.peerId { + if let forwardInfo = item.content.firstMessage.forwardInfo { + ignoreForward = true + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) } } - - if peerId != item.context.account.peerId { - if peerId.isGroupOrChannel && effectiveAuthor != nil { - var isBroadcastChannel = false - if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - allowFullWidth = true - } - - if !isBroadcastChannel { - hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) - } - } - } else if incoming { - hasAvatar = true + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { + if firstMessage.forwardInfo?.author?.id == source.id { + ignoreForward = true } + effectiveAuthor = source + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else { + effectiveAuthor = firstMessage.author + displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil + if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { + displayAuthorInfo = false + } + } + + if peerId != item.context.account.peerId { + if peerId.isGroupOrChannel && effectiveAuthor != nil { + var isBroadcastChannel = false + if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + allowFullWidth = true + } + + if !isBroadcastChannel { + hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) + } + } + } else if incoming { + hasAvatar = true + } } if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser { @@ -930,22 +945,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) - var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] + var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]? let contentNodeMessagesAndClasses = contentNodeMessagesAndClassesForItem(item) - for (contentNodeMessage, contentNodeClass, attributes) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, attributes, isAttachment) in contentNodeMessagesAndClasses { var found = false for (currentMessage, currentClass, supportsMosaic, currentLayout) in currentContentClassesPropertiesAndLayouts { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, currentLayout)) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, isAttachment, currentLayout)) found = true break } } if !found { let contentNode = (contentNodeClass as! ChatMessageBubbleContentNode.Type).init() - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, contentNode.asyncLayoutContent())) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, isAttachment, contentNode.asyncLayoutContent())) if addedContentNodes == nil { addedContentNodes = [] } @@ -988,7 +1003,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = attribute.title } } else if let attribute = attribute as? ReplyMessageAttribute { - replyMessage = firstMessage.associatedMessages[attribute.messageId] + if case let .replyThread(replyThreadMessageId, _) = item.chatLocation, replyThreadMessageId == attribute.messageId { + } else { + replyMessage = firstMessage.associatedMessages[attribute.messageId] + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview { replyMarkup = attribute } @@ -998,7 +1016,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = nil } - var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] + var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, Bool, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] let topNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedTop.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) let bottomNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedBottom.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) @@ -1046,12 +1064,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var index = 0 - for (message, _, attributes, prepareLayout) in contentPropertiesAndPrepareLayouts { + for (message, _, attributes, isAttachment, prepareLayout) in contentPropertiesAndPrepareLayouts { let topPosition: ChatMessageBubbleRelativePosition let bottomPosition: ChatMessageBubbleRelativePosition - topPosition = .Neighbour - bottomPosition = .Neighbour + topPosition = .Neighbour(false) + bottomPosition = .Neighbour(false) let prepareContentPosition: ChatMessageBubblePreparePosition if let mosaicRange = mosaicRange, mosaicRange.contains(index) { @@ -1060,6 +1078,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let refinedBottomPosition: ChatMessageBubbleRelativePosition if index == contentPropertiesAndPrepareLayouts.count - 1 { refinedBottomPosition = .None(.Left) + } else if index == contentPropertiesAndPrepareLayouts.count - 2 && contentPropertiesAndPrepareLayouts[contentPropertiesAndPrepareLayouts.count - 1].3 { + refinedBottomPosition = .None(.Left) } else { refinedBottomPosition = bottomPosition } @@ -1091,7 +1111,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude)) maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth) - contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, nodeLayout)) + contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, isAttachment, nodeLayout)) switch properties.hidesBackground { case .never: @@ -1177,7 +1197,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let firstNodeTopPosition: ChatMessageBubbleRelativePosition if displayHeader { - firstNodeTopPosition = .Neighbour + firstNodeTopPosition = .Neighbour(false) } else { firstNodeTopPosition = .None(topNodeMergeStatus) } @@ -1206,11 +1226,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1242,7 +1267,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } } - mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) + mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) } } @@ -1391,7 +1416,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode findRemoved: for i in 0 ..< currentContentClassesPropertiesAndLayouts.count { let currentMessage = currentContentClassesPropertiesAndLayouts[i].0 let currentClass: AnyClass = currentContentClassesPropertiesAndLayouts[i].1 - for (contentNodeMessage, contentNodeClass, _) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, _, _) in contentNodeMessagesAndClasses { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { continue findRemoved } @@ -1415,7 +1440,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } for i in 0 ..< contentPropertiesAndLayouts.count { - let (_, contentNodeProperties, preparePosition, contentNodeLayout) = contentPropertiesAndLayouts[i] + let (_, contentNodeProperties, preparePosition, isAttachment, contentNodeLayout) = contentPropertiesAndLayouts[i] if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize { let mosaicIndex = i - mosaicRange.lowerBound @@ -1466,7 +1491,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if mosaicRange.upperBound - 1 == contentNodeCount - 1 { lastMosaicBottomPosition = lastNodeTopPosition } else { - lastMosaicBottomPosition = .Neighbour + lastMosaicBottomPosition = .Neighbour(false) } if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition { @@ -1532,19 +1557,21 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if i == 0 { topPosition = firstNodeTopPosition } else { - topPosition = .Neighbour + topPosition = .Neighbour(false) } if i == contentNodeCount - 1 { bottomPosition = lastNodeTopPosition + } else if i == contentNodeCount - 2 && contentPropertiesAndLayouts[contentNodeCount - 1].3 { + bottomPosition = .Neighbour(true) } else { - bottomPosition = .Neighbour + bottomPosition = .Neighbour(false) } contentPosition = .linear(top: topPosition, bottom: bottomPosition) case .mosaic: assertionFailure() - contentPosition = .linear(top: .Neighbour, bottom: .Neighbour) + contentPosition = .linear(top: .Neighbour(false), bottom: .Neighbour(false)) } let (contentNodeWidth, contentNodeFinalize) = contentNodeLayout(CGSize(width: maximumNodeWidth, height: CGFloat.greatestFiniteMagnitude), contentPosition) #if DEBUG @@ -1757,7 +1784,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode replyInfoOriginY: CGFloat, removedContentNodeIndices: [Int]?, addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?, - contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes)], + contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, Bool)], contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)], mosaicStatusOrigin: CGPoint?, mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?, @@ -1964,7 +1991,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var sortedContentNodes: [ChatMessageBubbleContentNode] = [] - outer: for (message, nodeClass, _) in contentNodeMessagesAndClasses { + outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses { if let addedContentNodes = addedContentNodes { for (contentNodeMessage, contentNode) in addedContentNodes { if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId { diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift new file mode 100644 index 0000000000..827e5b6ab3 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -0,0 +1,267 @@ +import Foundation +import UIKit +import Postbox +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import TelegramPresentationData + +final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { + private let separatorNode: ASDisplayNode + private let textNode: TextNode + private let iconNode: ASImageNode + private let arrowNode: ASImageNode + private let buttonNode: HighlightTrackingButtonNode + private let avatarsNode: MergedAvatarsNode + + required init() { + self.separatorNode = ASDisplayNode() + self.separatorNode.isUserInteractionEnabled = false + + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.contentMode = .topLeft + self.textNode.contentsScale = UIScreenScale + self.textNode.displaysAsynchronously = true + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.isUserInteractionEnabled = false + + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.isUserInteractionEnabled = false + + self.avatarsNode = MergedAvatarsNode() + self.avatarsNode.isUserInteractionEnabled = false + + self.buttonNode = HighlightTrackingButtonNode() + + super.init() + + self.addSubnode(self.separatorNode) + self.addSubnode(self.textNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.arrowNode) + self.addSubnode(self.buttonNode) + self.addSubnode(self.avatarsNode) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + let nodes: [ASDisplayNode] = [ + strongSelf.textNode, + strongSelf.iconNode, + strongSelf.avatarsNode, + strongSelf.arrowNode, + ] + for node in nodes { + if highlighted { + node.layer.removeAnimation(forKey: "opacity") + node.alpha = 0.4 + } else { + node.alpha = 1.0 + node.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func buttonPressed() { + guard let item = self.item else { + return + } + item.controllerInteraction.openMessageReplies(item.message.id) + } + + override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { + let textLayout = TextNode.asyncLayout(self.textNode) + + return { item, layoutConstants, _, _, constrainedSize in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + + var maxTextWidth = CGFloat.greatestFiniteMagnitude + for media in item.message.media { + if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_background" || content.type == "telegram_theme" { + maxTextWidth = layoutConstants.wallpapers.maxTextWidth + break + } + } + + let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + + var dateReplies = 0 + var replyPeers: [Peer] = [] + for attribute in item.message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + dateReplies = Int(attribute.count) + replyPeers = attribute.latestUsers.compactMap { peerId -> Peer? in + return item.message.peers[peerId] + } + } + } + + //TODO:localize + let rawText: String + + if dateReplies > 0 { + if dateReplies == 1 { + rawText = "1 Comment" + } else { + rawText = "\(dateReplies) Comments" + } + } else { + rawText = "Leave a Comment" + } + + let imageSize: CGFloat = 30.0 + let imageSpacing: CGFloat = 20.0 + + var textLeftInset: CGFloat = 0.0 + if replyPeers.isEmpty { + textLeftInset = 32.0 + } else { + textLeftInset = 8.0 + imageSize * min(1.0, CGFloat(replyPeers.count)) + imageSpacing * max(0.0, min(2.0, CGFloat(replyPeers.count - 1))) + } + + let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset - textLeftInset - 20.0), height: constrainedSize.height) + + let attributedText: NSAttributedString + + let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing + + let textFont = item.presentationData.messageFont + + attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.accentTextColor) + + let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) + + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) + + var textFrame = CGRect(origin: CGPoint(x: -textInsets.left + textLeftInset, y: -textInsets.top + 5.0), size: textLayout.size) + var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) + + textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top - 2.0) + textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) + + var suggestedBoundingWidth: CGFloat + suggestedBoundingWidth = textFrameWithoutInsets.width + suggestedBoundingWidth += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + textLeftInset + 20.0 + + let iconImage = PresentationResourcesChat.chatMessageCommentsIcon(item.presentationData.theme.theme, incoming: incoming) + let arrowImage = PresentationResourcesChat.chatMessageCommentsArrowIcon(item.presentationData.theme.theme, incoming: incoming) + + return (suggestedBoundingWidth, { boundingWidth in + var boundingSize: CGSize + + boundingSize = textFrameWithoutInsets.size + boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + boundingSize.height = 42.0 + + return (boundingSize, { [weak self] animation, synchronousLoad in + if let strongSelf = self { + strongSelf.item = item + + let cachedLayout = strongSelf.textNode.cachedLayout + + if case .System = animation { + if let cachedLayout = cachedLayout { + if !cachedLayout.areLinesEqual(to: textLayout) { + if let textContents = strongSelf.textNode.contents { + let fadeNode = ASDisplayNode() + fadeNode.displaysAsynchronously = false + fadeNode.contents = textContents + fadeNode.frame = strongSelf.textNode.frame + fadeNode.isLayerBacked = true + strongSelf.addSubnode(fadeNode) + fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in + fadeNode?.removeFromSupernode() + }) + strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + } + } + + strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview + let _ = textApply() + + let adjustedTextFrame = textFrame + + strongSelf.textNode.frame = adjustedTextFrame + + if let iconImage = iconImage { + strongSelf.iconNode.image = iconImage + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 7.0), size: iconImage.size) + } + + if let arrowImage = arrowImage { + strongSelf.arrowNode.image = arrowImage + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: boundingWidth - 27.0, y: 8.0), size: arrowImage.size) + } + + strongSelf.iconNode.isHidden = !replyPeers.isEmpty + + let avatarsFrame = CGRect(origin: CGPoint(x: 10.0, y: 5.0), size: CGSize(width: imageSize * 3.0, height: imageSize)) + strongSelf.avatarsNode.frame = avatarsFrame + strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) + strongSelf.avatarsNode.update(context: item.context, peers: replyPeers, synchronousLoad: synchronousLoad, imageSize: imageSize, imageSpacing: imageSpacing, borderWidth: 2.0) + + strongSelf.separatorNode.backgroundColor = messageTheme.polls.separator + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: boundingWidth, height: UIScreenPixel)) + + strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: boundingSize.height)) + } + }) + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + } + + override func animateInsertionIntoBubble(_ duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if self.buttonNode.frame.contains(point) { + return .ignore + } + return .none + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.buttonNode.frame.contains(point) { + return self.buttonNode.view + } + return nil + } +} + + + + diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index a740794c22..6bfef25e69 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -146,11 +146,16 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -191,7 +196,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index 0d6f734525..e14086a7e8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -160,12 +160,15 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { private var reactionNodes: [StatusReactionNode] = [] private var reactionCountNode: TextNode? private var reactionButtonNode: HighlightTrackingButtonNode? + private var repliesIcon: ASImageNode? + private var replyCountNode: TextNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? private var layoutSize: CGSize? var openReactions: (() -> Void)? + var openReplies: (() -> Void)? override init() { self.dateNode = TextNode() @@ -177,7 +180,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { self.addSubnode(self.dateNode) } - func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> Void) { + func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) var checkReadNode = self.checkReadNode @@ -187,15 +190,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { var currentBackgroundNode = self.backgroundNode var currentImpressionIcon = self.impressionIcon + var currentRepliesIcon = self.repliesIcon let currentType = self.type let currentTheme = self.theme let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode) + let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) let previousLayoutSize = self.layoutSize - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount in let dateColor: UIColor var backgroundImage: UIImage? var outgoingStatus: ChatMessageDateAndStatusOutgoingType? @@ -206,6 +211,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let clockFrameImage: UIImage? let clockMinImage: UIImage? var impressionImage: UIImage? + var repliesImage: UIImage? let themeUpdated = presentationData.theme != currentTheme || type != currentType @@ -226,6 +232,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.incomingDateAndStatusImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.incomingDateAndStatusRepliesIcon + } case let .BubbleOutgoing(status): dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor outgoingStatus = status @@ -237,6 +246,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.outgoingDateAndStatusImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.outgoingDateAndStatusRepliesIcon + } case .ImageIncoming: dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor backgroundImage = graphics.dateAndStatusMediaBackground @@ -248,6 +260,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.mediaRepliesIcon + } case let .ImageOutgoing(status): dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor outgoingStatus = status @@ -260,6 +275,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.mediaRepliesIcon + } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -272,6 +290,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.freeRepliesIcon + } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -285,6 +306,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.freeRepliesIcon + } } var updatedDateText = dateText @@ -323,6 +347,20 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { currentImpressionIcon = nil } + var repliesIconSize = CGSize() + if let repliesImage = repliesImage { + if currentRepliesIcon == nil { + let iconNode = ASImageNode() + iconNode.isLayerBacked = true + iconNode.displayWithoutProcessing = true + iconNode.displaysAsynchronously = false + currentRepliesIcon = iconNode + } + repliesIconSize = repliesImage.size + } else { + currentRepliesIcon = nil + } + if let outgoingStatus = outgoingStatus { switch outgoingStatus { case .Sending: @@ -433,6 +471,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let reactionSize: CGFloat = 14.0 var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? + var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? let reactionSpacing: CGFloat = -4.0 let reactionTrailingSpacing: CGFloat = 4.0 @@ -458,6 +497,22 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += max(10.0, layoutAndApply.0.size.width) + 2.0 reactionCountLayoutAndApply = layoutAndApply } + + if replyCount > 0 { + let countString: String + if replyCount > 1000000 { + countString = "\(replyCount / 1000000)M" + } else if replyCount > 1000 { + countString = "\(replyCount / 1000)K" + } else { + countString = "\(replyCount)" + } + + let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) + reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 + replyCountLayoutAndApply = layoutAndApply + } + leftInset += reactionInset let layoutSize = CGSize(width: leftInset + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) @@ -510,7 +565,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { strongSelf.impressionIcon = nil } - strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset), size: date.size) if let clockFrameNode = clockFrameNode { @@ -677,6 +731,48 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } + if let currentRepliesIcon = currentRepliesIcon { + currentRepliesIcon.displaysAsynchronously = !presentationData.isPreview + if currentRepliesIcon.image !== repliesImage { + currentRepliesIcon.image = repliesImage + } + if currentRepliesIcon.supernode == nil { + strongSelf.repliesIcon = currentRepliesIcon + strongSelf.addSubnode(currentRepliesIcon) + } + currentRepliesIcon.frame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + floor((date.size.height - repliesIconSize.height) / 2.0)), size: repliesIconSize) + reactionOffset += 9.0 + } else if let repliesIcon = strongSelf.repliesIcon { + repliesIcon.removeFromSupernode() + strongSelf.repliesIcon = nil + } + + if let (layout, apply) = replyCountLayoutAndApply { + let node = apply() + if strongSelf.replyCountNode !== node { + strongSelf.replyCountNode?.removeFromSupernode() + strongSelf.addSubnode(node) + strongSelf.replyCountNode = node + if animated { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + node.frame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size) + reactionOffset += 4.0 + layout.size.width + } else if let replyCountNode = strongSelf.replyCountNode { + strongSelf.replyCountNode = nil + if animated { + if let previousLayoutSize = previousLayoutSize { + replyCountNode.frame = replyCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0) + } + replyCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyCountNode] _ in + replyCountNode?.removeFromSupernode() + }) + } else { + replyCountNode.removeFromSupernode() + } + } + /*if !strongSelf.reactionNodes.isEmpty { if strongSelf.reactionButtonNode == nil { let reactionButtonNode = HighlightTrackingButtonNode() @@ -709,17 +805,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } - static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { + static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { let currentLayout = node?.asyncLayout() - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies in let resultNode: ChatMessageDateAndStatusNode let resultSizeAndApply: (CGSize, (Bool) -> Void) if let node = node, let currentLayout = currentLayout { resultNode = node - resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) + resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) } else { resultNode = ChatMessageDateAndStatusNode() - resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) + resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) } return (resultSizeAndApply.0, { animated in diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 14cd68d6ef..d9bb1312d3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -178,24 +178,29 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let avatarInset: CGFloat var hasAvatar = false + let messagePeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - if peerId != item.context.account.peerId { - if peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true - } + case let .peer(peerId): + messagePeerId = peerId + case let .replyThread(messageId, _): + messagePeerId = messageId.peerId + } + + do { + if messagePeerId != item.context.account.peerId { + if messagePeerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true } - } else if incoming { - hasAvatar = true } - /*case .group: - hasAvatar = true*/ + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -332,7 +337,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let _ = attribute as? InlineBotMessageAttribute { } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index db12ec38fc..2054b91a02 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -294,11 +294,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -317,7 +322,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount) - let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions) + let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 7a89d3ca2e..eafec61d64 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -251,11 +251,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } let sentViaBot = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -280,7 +285,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { maxDateAndStatusWidth = width - videoFrame.midX - 85.0 } - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var contentSize = imageSize var dateAndStatusOverflow = false diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 548c80a5d9..4532f710ed 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -268,29 +268,34 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var effectiveAuthor: Peer? let displayAuthorInfo: Bool + let messagePeerId: PeerId switch chatLocation { - case let .peer(peerId): - if peerId == context.account.peerId { - if let forwardInfo = content.firstMessage.forwardInfo { - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) - } + case let .peer(peerId): + messagePeerId = peerId + case let .replyThread(messageId, _): + messagePeerId = messageId.peerId + } + + do { + let peerId = messagePeerId + if peerId == context.account.peerId { + if let forwardInfo = content.firstMessage.forwardInfo { + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) } - displayAuthorInfo = incoming && effectiveAuthor != nil - } else { - effectiveAuthor = content.firstMessage.author - for attribute in content.firstMessage.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] - break - } - } - displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil } - /*case .group: + displayAuthorInfo = incoming && effectiveAuthor != nil + } else { effectiveAuthor = content.firstMessage.author - displayAuthorInfo = incoming && effectiveAuthor != nil*/ + for attribute in content.firstMessage.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] + break + } + } + displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil + } } self.effectiveAuthorId = effectiveAuthor?.id diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 192883b28f..de02d97882 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -157,7 +157,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { if activeLiveBroadcastingTimeout != nil || selectedMedia?.venue != nil { var relativePosition = position if case let .linear(top, _) = position { - relativePosition = .linear(top: top, bottom: ChatMessageBubbleRelativePosition.Neighbour) + relativePosition = .linear(top: top, bottom: .Neighbour(false)) } imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: relativePosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius, layoutConstants: layoutConstants, chatPresentationData: item.presentationData) @@ -176,11 +176,16 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -241,7 +246,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 577556c523..3fbace1ef7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -174,6 +174,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { if case .mosaic = preparePosition { @@ -182,6 +183,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -226,7 +231,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index bb6149c63f..2668adcf64 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1024,11 +1024,16 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1069,7 +1074,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } @@ -1509,10 +1514,10 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0) } - let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - mergedImageSize) / 2.0)), size: CGSize(width: mergedImageSize + mergedImageSpacing * 2.0, height: mergedImageSize)) + let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - defaultMergedImageSize) / 2.0)), size: CGSize(width: defaultMergedImageSize + defaultMergedImageSpacing * 2.0, height: defaultMergedImageSize)) strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) - strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad) + strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing, borderWidth: defaultBorderWidth) strongSelf.avatarsNode.isHidden = isBotChat let alphaTransition: ContainedViewLayoutTransition if animation.isAnimated { @@ -1787,23 +1792,33 @@ private extension PeerAvatarReference { private final class MergedAvatarsNodeArguments: NSObject { let peers: [PeerAvatarReference] let images: [PeerId: UIImage] + let imageSize: CGFloat + let imageSpacing: CGFloat + let borderWidth: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage]) { + init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { self.peers = peers self.images = images + self.imageSize = imageSize + self.imageSpacing = imageSpacing + self.borderWidth = borderWidth } } -private let mergedImageSize: CGFloat = 16.0 -private let mergedImageSpacing: CGFloat = 15.0 +private let defaultMergedImageSize: CGFloat = 16.0 +private let defaultMergedImageSpacing: CGFloat = 15.0 +private let defaultBorderWidth: CGFloat = 1.0 private let avatarFont = avatarPlaceholderFont(size: 8.0) -private final class MergedAvatarsNode: ASDisplayNode { +final class MergedAvatarsNode: ASDisplayNode { private var peers: [PeerAvatarReference] = [] private var images: [PeerId: UIImage] = [:] private var disposables: [PeerId: Disposable] = [:] private let buttonNode: HighlightTrackingButtonNode + private var imageSize: CGFloat = defaultMergedImageSize + private var imageSpacing: CGFloat = defaultMergedImageSpacing + private var borderWidthValue: CGFloat = defaultBorderWidth var pressed: (() -> Void)? @@ -1832,10 +1847,13 @@ private final class MergedAvatarsNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool) { + func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { + self.imageSize = imageSize + self.imageSpacing = imageSpacing + self.borderWidthValue = borderWidth var filteredPeers = peers.map(PeerAvatarReference.init) if filteredPeers.count > 3 { - let _ = filteredPeers.dropLast(filteredPeers.count - 3) + filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3) } if filteredPeers != self.peers { self.peers = filteredPeers @@ -1870,7 +1888,7 @@ private final class MergedAvatarsNode: ASDisplayNode { switch peer { case let .image(peerReference, representation): if self.disposables[peer.peerId] == nil { - if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: mergedImageSize, height: mergedImageSize), synchronousLoad: synchronousLoad) { + if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) { let disposable = (signal |> deliverOnMainQueue).start(next: { [weak self] imageVersions in guard let strongSelf = self else { @@ -1894,7 +1912,7 @@ private final class MergedAvatarsNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { - return MergedAvatarsNodeArguments(peers: self.peers, images: self.images) + return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -1912,13 +1930,16 @@ private final class MergedAvatarsNode: ASDisplayNode { return } - context.setBlendMode(.copy) + let mergedImageSize = parameters.imageSize + let mergedImageSpacing = parameters.imageSpacing var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize for i in (0 ..< parameters.peers.count).reversed() { let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize)) + context.setBlendMode(.copy) context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: imageRect.insetBy(dx: -1.0, dy: -1.0)) + context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth)) + context.setBlendMode(.normal) context.saveGState() switch parameters.peers[i] { @@ -1926,7 +1947,7 @@ private final class MergedAvatarsNode: ASDisplayNode { context.translateBy(x: currentX, y: 0.0) drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId) context.translateBy(x: -currentX, y: 0.0) - case let .image(reference): + case .image: if let image = parameters.images[parameters.peers[i].peerId] { context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index c7ec00047c..4abd2baa5a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -53,6 +53,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var rawText = "" + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden @@ -60,6 +61,10 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { viewCount = attribute.count } else if let attribute = attribute as? RestrictedContentMessageAttribute { rawText = attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) ?? "" + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -100,7 +105,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 3d4d47aeba..29320c50b2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -249,8 +249,21 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - /*case .group: - hasAvatar = true*/ + case let .replyThread(messageId, _): + if messageId.peerId != item.context.account.peerId { + if messageId.peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true + } + } + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -337,11 +350,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -360,7 +378,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -393,7 +411,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index b36dc9ba26..e9d1981bb0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -109,11 +109,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -133,28 +138,38 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount) let statusType: ChatMessageDateAndStatusType? + var displayStatus = false switch position { - case .linear(_, .None): - if incoming { - statusType = .BubbleIncoming - } else { - if message.flags.contains(.Failed) { - statusType = .BubbleOutgoing(.Failed) - } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { - statusType = .BubbleOutgoing(.Sending) - } else { - statusType = .BubbleOutgoing(.Sent(read: item.read)) - } + case let .linear(_, neighbor): + if case .None = neighbor { + displayStatus = true + } else if case .Neighbour(true) = neighbor { + displayStatus = true } default: - statusType = nil + break + } + if displayStatus { + if incoming { + statusType = .BubbleIncoming + } else { + if message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { + statusType = .BubbleOutgoing(.Sending) + } else { + statusType = .BubbleOutgoing(.Sent(read: item.read)) + } + } + } else { + statusType = nil } var statusSize: CGSize? var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 40598db1b3..9d74eed519 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -120,6 +120,7 @@ final class ChatPanelInterfaceInteraction { let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let openPeersNearby: () -> Void let unarchivePeer: () -> Void + let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? init( @@ -193,6 +194,7 @@ final class ChatPanelInterfaceInteraction { openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, + viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage @@ -265,6 +267,7 @@ final class ChatPanelInterfaceInteraction { self.openPeersNearby = openPeersNearby self.displaySearchResultsTooltip = displaySearchResultsTooltip self.unarchivePeer = unarchivePeer + self.viewReplies = viewReplies self.statuses = statuses } } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 960a17e718..016edcb7d3 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -115,6 +115,16 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor } + let isReplyThread: Bool + if case .replyThread = interfaceState.chatLocation { + isReplyThread = true + } else { + isReplyThread = false + } + + self.closeButton.isHidden = isReplyThread + self.tapButton.isUserInteractionEnabled = !isReplyThread + var messageUpdated = false if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion { @@ -129,7 +139,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentMessage = interfaceState.pinnedMessage if let currentMessage = currentMessage, let currentLayout = self.currentLayout { - self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil) + self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread) } } @@ -148,14 +158,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentLayout = (width, leftInset, rightInset) if let currentMessage = self.currentMessage { - self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true) + self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread) } } return panelHeight } - private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool) { + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) let imageNodeLayout = self.imageNode.asyncLayout() @@ -204,6 +214,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } } + if isReplyThread { + if let author = message.effectiveAuthor { + titleString = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) + } else { + titleString = "" + } + } + var applyImage: (() -> Void)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: 35.0, height: 35.0) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index 2be30eb2bf..c662778197 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -125,7 +125,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.navigationItem.titleView = self.titleView diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 501b521a41..5fd3198a81 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -451,6 +451,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index 72086329da..da6f48c5d9 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -118,7 +118,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer let action = TelegramMediaActionType.titleUpdated(title: new) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeAbout(prev, new): var peers = SimpleDictionary() @@ -149,14 +149,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] - let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): @@ -187,7 +187,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var previousAttributes: [MessageAttribute] = [] @@ -205,8 +205,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { attributes.append(TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< text.count, type: .Italic)])) } - let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): @@ -225,7 +225,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.photoUpdated(image: photo) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleInvites(value): var peers = SimpleDictionary() @@ -252,7 +252,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleSignatures(value): var peers = SimpleDictionary() @@ -279,7 +279,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updatePinned(message): switch self.id.contentIndex { @@ -303,7 +303,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: if let message = message { @@ -321,7 +321,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { var peers = SimpleDictionary() @@ -343,7 +343,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } @@ -388,7 +388,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -405,7 +405,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): @@ -431,7 +431,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -455,7 +455,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case .participantJoin, .participantLeave: @@ -473,7 +473,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } else { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantInvite(participant): var peers = SimpleDictionary() @@ -490,7 +490,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() @@ -620,7 +620,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() @@ -756,7 +756,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeStickerPack(_, new): var peers = SimpleDictionary() @@ -785,7 +785,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() @@ -815,7 +815,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() @@ -873,7 +873,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .pollStopped(message): switch self.id.contentIndex { @@ -901,7 +901,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -918,7 +918,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): @@ -974,7 +974,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() @@ -996,12 +996,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case let .updateSlowmode(_, newValue): @@ -1032,7 +1032,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index 420496db61..d4326f4f89 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import SearchBarNode import LocalizedPeerData import SwiftSignalKit +import AccountContext private let searchBarFont = Font.regular(17.0) @@ -31,10 +32,8 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern) let placeholderText: String switch chatLocation { - case .peer: + case .peer, .replyThread: placeholderText = strings.Conversation_SearchPlaceholder - /*case .group: - placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -94,10 +93,8 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar.prefixString = nil let placeholderText: String switch self.chatLocation { - case .peer: + case .peer, .replyThread: placeholderText = self.strings.Conversation_SearchPlaceholder - /*case .group: - placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) case .members: diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift index 4b420cf10f..0123da2cd2 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift @@ -69,9 +69,11 @@ final class ChatSendMessageActionSheetController: ViewController { var reminders = false var isSecret = false + var canSchedule = false if case let .peer(peerId) = self.interfaceState.chatLocation { reminders = peerId == context.account.peerId isSecret = peerId.namespace == Namespaces.Peer.SecretChat + canSchedule = !isSecret } self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, reminders: reminders, gesture: gesture, sendButtonFrame: self.sendButtonFrame, textInputNode: self.textInputNode, forwardedCount: forwardedCount, send: { [weak self] in @@ -80,7 +82,7 @@ final class ChatSendMessageActionSheetController: ViewController { }, sendSilently: { [weak self] in self?.controllerInteraction?.sendCurrentMessage(true) self?.dismiss(cancel: false) - }, schedule: isSecret ? nil : { [weak self] in + }, schedule: !canSchedule ? nil : { [weak self] in self?.controllerInteraction?.scheduleCurrentMessage() self?.dismiss(cancel: false) }, cancel: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 5a21dcb5f2..9092f2eff5 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -790,6 +790,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { placeholder = interfaceState.strings.Conversation_InputTextBroadcastPlaceholder } + } else if case .replyThread = interfaceState.chatLocation { + //TODO:localize + placeholder = "Reply" } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholder } diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 8228544ff7..73c03b865f 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -18,7 +18,12 @@ import PhoneNumberFormat import ChatTitleActivityNode enum ChatTitleContent { - case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool) + enum ReplyThreadType { + case replies + case comments + } + + case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool, repleThread: ReplyThreadType?) case group([Peer]) case custom(String) } @@ -100,8 +105,19 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var titleScamIcon = false var isEnabled = true switch titleContent { - case let .peer(peerView, _, isScheduledMessages): - if isScheduledMessages { + case let .peer(peerView, _, isScheduledMessages, replyThreadType): + if let replyThreadType = replyThreadType { + //TODO:localize + let typeText: String + switch replyThreadType { + case .replies: + typeText = "Replies" + case .comments: + typeText = "Comments" + } + string = NSAttributedString(string: typeText, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) + isEnabled = false + } else if isScheduledMessages { if peerView.peerId == self.account.peerId { string = NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) } else { @@ -178,9 +194,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, isScheduledMessages): + case let .peer(peerView, _, isScheduledMessages, replyThreadType): if let peer = peerViewMainPeer(peerView) { - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || replyThreadType != nil || peer.id.id == 708513 { inputActivitiesAllowed = false } } @@ -266,10 +282,10 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, onlineMemberCount, isScheduledMessages): + case let .peer(peerView, onlineMemberCount, isScheduledMessages, replyThreadType): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || replyThreadType != nil || peer.id.id == 708513 { let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) } else if let user = peer as? TelegramUser { diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 31a2758560..5bbb1c44de 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -144,6 +144,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 83b2486125..b09a06020e 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -125,6 +125,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if message.id.peerId == peerId { return true } + case let .replyThread(messageId, _): + if message.id.peerId == messageId.peerId { + return true + } } } } diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift index d02e1b279c..10c70689a3 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift @@ -133,6 +133,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) @@ -161,7 +162,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none, isGlobalSearch: isGlobalSearch)) + let chatLocationContextHolder = Atomic(value: nil) + + self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none, isGlobalSearch: isGlobalSearch)) super.init() @@ -491,7 +494,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, isGlobalSearch: self.isGlobalSearch)) + let chatLocationContextHolder = Atomic(value: nil) + let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, isGlobalSearch: self.isGlobalSearch))) historyNode.preloadPages = true historyNode.stackFromBottom = true historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 6e5c291470..66bba913dc 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -71,7 +71,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds } self.selectedMessagesPromise.set(.single(self.selectedMessages)) - self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, isGlobalSearch: false)) + let chatLocationContextHolder = Atomic(value: nil) + self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, isGlobalSearch: false)) self.listNode.defaultToSynchronousTransactionWhileScrolling = true if tagMask == .music { @@ -268,7 +269,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 10a0af6a6c..d3f864847e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -428,7 +428,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -1954,6 +1954,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index 8d0a8134ae..1efca4fc26 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -5,6 +5,7 @@ import TelegramCore import SyncCore import Display import MergeLists +import AccountContext func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool) -> ChatHistoryViewTransition { let mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 38fbdc29fd..b01e248921 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -915,8 +915,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.navigateToChatImpl(accountId, peerId, messageId) } - public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { - let historyView = preloadedChatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) + public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { + let historyView = preloadedChatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) return historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -1097,7 +1097,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? { - return peerSharedMediaControllerImpl(context: context, peerId: peerId) + return nil } public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController { @@ -1194,6 +1194,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,