From 3ce113465d5b0e65e7db8c7eab14ce03bf7a8c8a Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 25 Aug 2020 17:35:21 +0100 Subject: [PATCH 1/5] Initial reply stats implementation --- Telegram/NotificationService/Serialization.m | 2 +- .../Sources/PhotoResources.swift | 8 +- .../Postbox/Sources/MessageHistoryView.swift | 12 +- .../Sources/MessageHistoryViewState.swift | 8 +- .../Sources/MessageOfInterestHolesView.swift | 8 +- submodules/Postbox/Sources/Postbox.swift | 14 +- .../BubbleSettingsController.swift | 2 +- .../SettingsUI/Sources/DebugController.swift | 1 + .../TextSizeSelectionController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../Themes/ThemeSettingsChatPreviewItem.swift | 2 +- .../Sources/ReplyMessageAttribute.swift | 32 +++- .../Sources/ReplyThreadMessageAttribute.swift | 18 ++ submodules/TelegramApi/Sources/Api0.swift | 4 +- submodules/TelegramApi/Sources/Api1.swift | 102 ++++++----- submodules/TelegramApi/Sources/Api3.swift | 159 ++++++++++-------- .../TelegramCore/Sources/AccountManager.swift | 1 + .../Sources/AccountStateManagementUtils.swift | 33 +++- .../Sources/AccountViewTracker.swift | 7 +- .../Sources/ApplyUpdateMessage.swift | 17 +- .../TelegramCore/Sources/DeleteMessages.swift | 20 ++- .../TelegramCore/Sources/EnqueueMessage.swift | 6 +- ...ecretChatIncomingDecryptedOperations.swift | 6 +- .../Sources/ReplyThreadHistory.swift | 31 ++++ .../TelegramCore/Sources/Serialization.swift | 2 +- .../Sources/StoreMessage_Telegram.swift | 18 +- .../Sources/UpdateMessageMedia.swift | 20 +++ .../Sources/UpdateMessageService.swift | 4 +- .../PresentationThemeEssentialGraphics.swift | 16 ++ .../Message/ReplyCount.imageset/Contents.json | 12 ++ .../Message/ReplyCount.imageset/replies.pdf | Bin 0 -> 4117 bytes .../ChatMessageAnimatedStickerItemNode.swift | 5 +- .../ChatMessageAttachedContentNode.swift | 5 +- .../Sources/ChatMessageBubbleItemNode.swift | 7 +- .../ChatMessageContactBubbleContentNode.swift | 5 +- .../ChatMessageDateAndStatusNode.swift | 110 +++++++++++- .../ChatMessageInteractiveFileNode.swift | 5 +- ...atMessageInteractiveInstantVideoNode.swift | 5 +- .../ChatMessageMapBubbleContentNode.swift | 5 +- .../ChatMessageMediaBubbleContentNode.swift | 5 +- .../ChatMessagePollBubbleContentNode.swift | 5 +- ...atMessageRestrictedBubbleContentNode.swift | 5 +- .../Sources/ChatMessageStickerItemNode.swift | 5 +- .../ChatMessageTextBubbleContentNode.swift | 5 +- 45 files changed, 568 insertions(+), 175 deletions(-) create mode 100644 submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift create mode 100644 submodules/TelegramCore/Sources/ReplyThreadHistory.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf 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/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index b8e3aa46fd..4980951cd6 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -32,9 +32,9 @@ public func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> Telegr private let progressiveRangeMap: [(Int, [Int])] = [ (100, [0]), - (400, [2]), - (600, [4, 5]), - (Int(Int32.max), [4, 5, 8, 9]) + (400, [1]), + (600, [2, 3]), + (Int(Int32.max), [2, 3, 4]) ] public func representationFetchRangeForDisplayAtSize(representation: TelegramMediaImageRepresentation, dimension: Int) -> Range? { @@ -52,7 +52,7 @@ public func representationFetchRangeForDisplayAtSize(representation: TelegramMed } public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false) -> Signal, NoError> { - if let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count == 10 { + if let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count == 5 { enum SizeSource { case miniThumbnail(data: Data) case image(size: Int) diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 87c2e0db6a..895cdbfe4f 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -236,7 +236,13 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } -public enum MessageHistoryViewPeerIds: Equatable { +public protocol MessageHistoryViewExternalInput: class { + func getNamespaces() -> [MessageId.Namespace] + func getMessageIds(namespace: MessageId.Namespace) -> [MessageId] + func getHoleIndices(namespace: MessageId.Namespace) -> IndexSet +} + +public enum MessageHistoryViewInput: Equatable { case single(PeerId) case associated(PeerId, MessageId?) } @@ -254,7 +260,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 +280,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 diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 7f46f97daa..f53136c21e 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -781,7 +781,7 @@ 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 @@ -1229,7 +1229,7 @@ final class HistoryViewLoadedState { } } -private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { +private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] var peerIds: [PeerId] = [] switch locations { @@ -1268,7 +1268,7 @@ final class HistoryViewLoadingState { 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?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { self.messageId = messageId self.tag = tag self.halfLimit = halfLimit @@ -1312,7 +1312,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)))) diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index caa7f03362..9efbe3090e 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,7 +43,7 @@ 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 @@ -127,7 +127,7 @@ 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) @@ -155,7 +155,7 @@ 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) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 4dcf6d4dcc..a9d559ebbd 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 { @@ -2333,8 +2341,8 @@ public final class Postbox { } } - func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewPeerIds { - var peerIds: MessageHistoryViewPeerIds + func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewInput { + var peerIds: MessageHistoryViewInput switch chatLocation { case let .peer(peerId): peerIds = .single(peerId) @@ -2410,7 +2418,7 @@ public final class Postbox { } } - 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? switch peerIds { diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 600f8741f7..0925787231 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -175,7 +175,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel 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: []) 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, 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=" 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/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 862a27987f..e3089a6248 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -321,7 +321,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView 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: []) 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, 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=" diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 2830ef49ba..32b8f2268b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -875,7 +875,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate 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: []) 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, 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: []) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index e6a3736b1f..9bca619021 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -480,7 +480,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { 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: []) 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, 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: []) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index df4f629682..975a874880 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -164,7 +164,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { 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: []) } - 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, 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/SyncCore/Sources/ReplyMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift index cbcc36b5fa..5ab64d24fe 100644 --- a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift @@ -3,23 +3,53 @@ 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 ReplyMessageAttribute { + var effectiveReplyThreadMessageId: MessageId { + return self.threadMessageId ?? self.messageId + } +} + +public extension Message { + var effectiveReplyThreadMessageId: MessageId? { + for attribute in self.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + return attribute.effectiveReplyThreadMessageId + } + } + return nil } } diff --git a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift new file mode 100644 index 0000000000..c7cc4f4da0 --- /dev/null +++ b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift @@ -0,0 +1,18 @@ +import Foundation +import Postbox + +public class ReplyThreadMessageAttribute: MessageAttribute { + public let count: Int32 + + public init(count: Int32) { + self.count = count + } + + required public init(decoder: PostboxDecoder) { + self.count = decoder.decodeInt32ForKey("c", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.count, forKey: "c") + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 9d6c66583c..ae34427ed6 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -388,7 +388,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } - dict[1831138451] = { return Api.MessageViews.parse_messageViews($0) } + dict[23453020] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) } dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($0) } @@ -602,7 +602,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1820043071] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } dict[-1642487306] = { return Api.Message.parse_messageService($0) } - dict[-181507201] = { return Api.Message.parse_message($0) } + dict[2119777911] = { return Api.Message.parse_message($0) } dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 5a30fc3b91..a67fd40605 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -11752,24 +11752,26 @@ public extension Api { } public enum MessageViews: TypeConstructorDescription { - case messageViews(views: Int32, forwards: Int32) + case messageViews(views: Int32, forwards: Int32, replies: Int32, repliesPts: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageViews(let views, let forwards): + case .messageViews(let views, let forwards, let replies, let repliesPts): if boxed { - buffer.appendInt32(1831138451) + buffer.appendInt32(23453020) } serializeInt32(views, buffer: buffer, boxed: false) serializeInt32(forwards, buffer: buffer, boxed: false) + serializeInt32(replies, buffer: buffer, boxed: false) + serializeInt32(repliesPts, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageViews(let views, let forwards): - return ("messageViews", [("views", views), ("forwards", forwards)]) + case .messageViews(let views, let forwards, let replies, let repliesPts): + return ("messageViews", [("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts)]) } } @@ -11778,10 +11780,16 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageViews.messageViews(views: _1!, forwards: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageViews.messageViews(views: _1!, forwards: _2!, replies: _3!, repliesPts: _4!) } else { return nil @@ -17162,7 +17170,7 @@ public extension Api { public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) - case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) + case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, replyToTopId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Int32?, repliesPts: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17184,9 +17192,9 @@ public extension Api { serializeInt32(date, buffer: buffer, boxed: false) action.serialize(buffer, true) break - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let editDate, let postAuthor, let groupedId, let restrictionReason): + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let repliesPts, let editDate, let postAuthor, let groupedId, let restrictionReason): if boxed { - buffer.appendInt32(-181507201) + buffer.appendInt32(2119777911) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -17195,6 +17203,7 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 24) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} serializeInt32(date, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} @@ -17206,6 +17215,8 @@ public extension Api { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {serializeInt32(replies!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {serializeInt32(repliesPts!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} @@ -17224,8 +17235,8 @@ public extension Api { return ("messageEmpty", [("id", id)]) case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let editDate, let postAuthor, let groupedId, let restrictionReason): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let repliesPts, let editDate, let postAuthor, let groupedId, let restrictionReason): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("replyToTopId", replyToTopId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) } } @@ -17293,34 +17304,40 @@ public extension Api { var _7: Int32? if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } var _8: Int32? - _8 = reader.readInt32() - var _9: String? - _9 = parseString(reader) - var _10: Api.MessageMedia? + if Int(_1!) & Int(1 << 24) != 0 {_8 = reader.readInt32() } + var _9: Int32? + _9 = reader.readInt32() + var _10: String? + _10 = parseString(reader) + var _11: Api.MessageMedia? if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _11 = Api.parse(reader, signature: signature) as? Api.MessageMedia } } - var _11: Api.ReplyMarkup? + var _12: Api.ReplyMarkup? if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + _12 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } - var _12: [Api.MessageEntity]? + var _13: [Api.MessageEntity]? if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _13: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_13 = reader.readInt32() } var _14: Int32? if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } var _15: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_15 = reader.readInt32() } - var _16: String? - if Int(_1!) & Int(1 << 16) != 0 {_16 = parseString(reader) } - var _17: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_17 = reader.readInt64() } - var _18: [Api.RestrictionReason]? + if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } + var _16: Int32? + if Int(_1!) & Int(1 << 23) != 0 {_16 = reader.readInt32() } + var _17: Int32? + if Int(_1!) & Int(1 << 23) != 0 {_17 = reader.readInt32() } + var _18: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } + var _19: String? + if Int(_1!) & Int(1 << 16) != 0 {_19 = parseString(reader) } + var _20: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_20 = reader.readInt64() } + var _21: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _21 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } let _c1 = _1 != nil let _c2 = _2 != nil @@ -17329,19 +17346,22 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 11) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = _8 != nil + let _c8 = (Int(_1!) & Int(1 << 24) == 0) || _8 != nil let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 10) == 0) || _13 != nil + let _c10 = _10 != nil + let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 7) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 15) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 16) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 17) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 22) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, editDate: _15, postAuthor: _16, groupedId: _17, restrictionReason: _18) + let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 23) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 23) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 22) == 0) || _21 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, replyToTopId: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, repliesPts: _17, editDate: _18, postAuthor: _19, groupedId: _20, restrictionReason: _21) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 67999cfce8..27251ed127 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -3629,24 +3629,6 @@ public extension Api { }) } - public static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1824339449) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {password!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("data", data), ("password", password)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in - let reader = BufferReader(buffer) - var result: Api.messages.BotCallbackAnswer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer - } - return result - }) - } - public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageViews]>) { let buffer = Buffer() buffer.appendInt32(-39035462) @@ -3667,22 +3649,19 @@ public extension Api { }) } - 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) { + public static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1934479725) + buffer.appendInt32(-1824339449) 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(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - 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 + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {password!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("data", data), ("password", password)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in let reader = BufferReader(buffer) - var result: Api.messages.Messages? + var result: Api.messages.BotCallbackAnswer? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages + result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer } return result }) @@ -3722,6 +3701,48 @@ public extension Api { 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) + filter.serialize(buffer, true) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + 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() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + 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(-39505956) + peer.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) + 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() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -5408,6 +5429,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) @@ -5442,34 +5491,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) @@ -6856,14 +6877,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() { @@ -6873,11 +6891,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/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index 9877a921ae..b19adab9a4 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..e193a30c6b 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -2274,9 +2274,34 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var topUpperHistoryBlockMessages: [PeerIdAndMessageNamespace: MessageId.Id] = [:] + var messageThreadStatsDifferences: [MessageId: Int] = [:] + func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int) { + if let value = messageThreadStatsDifferences[threadMessageId] { + messageThreadStatsDifferences[threadMessageId] = value + add - remove + } else { + messageThreadStatsDifferences[threadMessageId] = add - remove + } + } + 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 { + findAttribute: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if !transaction.messageExists(id: id) { + addMessageThreadStatsDifference(threadMessageId: attribute.effectiveReplyThreadMessageId, add: 1, remove: 0) + } + } + break findAttribute + } + } + } + } + } let _ = transaction.addMessages(messages, location: location) if case .UpperHistoryBlock = location { for message in messages { @@ -2384,7 +2409,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) + }) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false) @@ -2908,6 +2935,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } } + for (threadMessageId, difference) in messageThreadStatsDifferences { + updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference) + } + 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 9f166ff156..db01fd8b92 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -599,16 +599,17 @@ public final class AccountViewTracker { 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] { + 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 loop: for j in 0 ..< attributes.count { if let attribute = attributes[j] as? ViewCountMessageAttribute { attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views))) - } - if let _ = attributes[j] as? ForwardCountMessageAttribute { + } else if let _ = attributes[j] as? ForwardCountMessageAttribute { attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) + } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { + attributes[j] = ReplyThreadMessageAttribute(count: replies) } } 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)) diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index 9d09f0de6e..1ec9818c25 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -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,21 @@ 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, 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 { + for attribute in updatedMessage.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + updateMessageThreadStats(transaction: transaction, threadMessageId: attribute.effectiveReplyThreadMessageId, difference: 1) + break + } + } + } + } for file in sentStickers { transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } diff --git a/submodules/TelegramCore/Sources/DeleteMessages.swift b/submodules/TelegramCore/Sources/DeleteMessages.swift index 14dfab3fd3..f42329c25a 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,24 @@ 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) { + findAttribute: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if let manualAddMessageThreadStatsDifference = manualAddMessageThreadStatsDifference { + manualAddMessageThreadStatsDifference(attribute.messageId, 0, 1) + } else { + updateMessageThreadStats(transaction: transaction, threadMessageId: attribute.effectiveReplyThreadMessageId, difference: -1) + } + } + break findAttribute + } + } + } + } + } transaction.deleteMessages(ids, forEachMedia: { _ in }) } diff --git a/submodules/TelegramCore/Sources/EnqueueMessage.swift b/submodules/TelegramCore/Sources/EnqueueMessage.swift index b1fc81bf18..b7dab93053 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 { diff --git a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift index b86d428398..9b4d821ae6 100644 --- a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift @@ -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? @@ -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? @@ -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? diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift new file mode 100644 index 0000000000..89c331d2bc --- /dev/null +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -0,0 +1,31 @@ +import Foundation +import SyncCore +import Postbox +import SwiftSignalKit + +private class ReplyThreadHistoryContextImpl { + private let queue: Queue + private let account: Account + + struct NamespaceState: Equatable { + var sortedMessageIds: [MessageId] + var holeIndices: IndexSet + } + + init(queue: Queue, account: Account) { + self.queue = queue + self.account = account + } +} + +class ReplyThreadHistoryContext { + private let queue = Queue() + private let impl: QueueLocalObject + + public init(account: Account, peerId: PeerId, threadMessageId: MessageId) { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return ReplyThreadHistoryContextImpl(queue: queue, account: account) + }) + } +} diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index 9d7dec93c3..94cd1882c6 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 118 + return 119 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 02fd29efd6..2bf27e1f18 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -137,7 +137,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, _, media, _, entities, _, _, _, _, _, _, _, _): let peerId: PeerId switch toId { case let .peerUser(userId): @@ -241,7 +241,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { switch message { - case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyToMsgId = replyToMsgId { let peerId: PeerId switch toId { @@ -399,7 +399,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, forwards, editDate, postAuthor, groupingId, restrictionReason): + case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, replyToTopId, date, message, media, replyMarkup, entities, views, forwards, replies, _, editDate, postAuthor, groupingId, restrictionReason): let peerId: PeerId var authorId: PeerId? switch toId { @@ -515,7 +515,11 @@ extension StoreMessage { } if let replyToMsgId = replyToMsgId { - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) + var threadMessageId: MessageId? + if let replyToTopId = replyToTopId { + threadMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) + } + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) } if namespace != Namespaces.Message.ScheduledCloud { @@ -564,6 +568,10 @@ extension StoreMessage { attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) }*/ + if let replies = replies { + attributes.append(ReplyThreadMessageAttribute(count: replies)) + } + if let restrictionReason = restrictionReason { attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:)))) } @@ -636,7 +644,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 { diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 62e433f3f2..121f228cf0 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -28,3 +28,23 @@ func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) { }) } } + +func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int) { + 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 { + attributes[j] = ReplyThreadMessageAttribute(count: max(0, attribute.count + countDifference)) + updated = true + break loop + } + } + if !updated { + attributes.append(ReplyThreadMessageAttribute(count: max(0, countDifference))) + } + 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)) + }) +} diff --git a/submodules/TelegramCore/Sources/UpdateMessageService.swift b/submodules/TelegramCore/Sources/UpdateMessageService.swift index 7ca077332f..b79e3b5e56 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, repliesPts: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -75,7 +75,7 @@ class UpdateMessageService: NSObject, MTMessageService { generatedToId = Api.Peer.peerUser(userId: self.peerId.id) } - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, repliesPts: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { 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/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 0000000000000000000000000000000000000000..f55a72204ea6de627d9b6d809a60809d47017a07 GIT binary patch literal 4117 zcmai%cT`hbx5X(@ARvNL1W^x25v3%A08!~BQj{i0=m`nEXb4@Z6lv0ppn&uyMM0$n zq=Rq?ND~zhF(6H(2uQiUM7{5N-~HbB&dA8wW1V?scJ`Wo{FaD;hL$8$3I-Nwr>s#H zi`E|ww0DAK0SMrMaRy(!2uSPU+#Lvx0GwJf0;IK^TnRWl_3nx$;52Y{9#|Zpqy+XP z;Bja-urEC>`LeU6DqHy03YR%k;m{6o6cKt&=|$pu70mUU!I$RaVRp>be(Qp1It4*_ zV*aOXOkatox?GDszaLa({o(ApjYSxYvT)E)lBd8rE6FrUdQ&cdqdwQ!A%;SVarNR| zMB&_X)E{z1@R#w#&gcZx5yRi~iBE7t9urZuYbrfPf2e-wLEJ{Y@iW+(Bq3`rN-A%7 zAH>qt<=dC=t(|IY7%zKfg3nxBIm3(C1iuDP6&;9BF9um6yUg5NhgCXGq|6H2-l23l z;{ry^ND=Jtkax*G8ja7Ed$yJuC$xyJvfw!3^Y5qT`~5ZVYM%@vl%_{dcW<(}KFt~& zLq|UlfW-G23CdM<9DqF09bgPkA6g+{2POog9takNma!s*x_6K65y`hK=OeP32NrK|y3}t=y&|;k<|$9j zr(1WO*w3$=noO@)sUc78rb#_}S7UK|HNxlo;Rn;pGgF~ln9{4m;hZn?W?+Fir#3ct zSElnGf^qKHUwxvkXziu-ly;Ye!e|d^OPSv~f7S0g4v;oLJN)>@}Lv$aGoAs zcsraYAosJN?%__LKKBG@$uXiO;+G%oIn9@rBm=yMoiUC8SWuT5T7VTGt?uFKfj7R6 zw!;B5fx0IYkpE@yn}@=09>3Cv_@R=%Lfw$GDlL)j)Hnjt8aQt!JDd?x?Y{>#^RCZT z<8huSGD)5^a6||Y+2HqQ0bX6W#0@fx)($aXhzu9?);OTW#VKxzz50S486{+=$;Emr z3^T^E#$nLeVyK}>nTT^?St*CFPiE|Qp{B;?o{x^dYi2W?Y{a zTP%+p?QP#;IL1NGZbtuN+TI?7+}2N)y7I7vue zUHBj!SaU?~Vvr~=g(20rx`R*VJLu&`nz&3PRE8&qCHIlgOnas4X3^}qhMs#w(T_R& zsD8di*{rw*gUJk$>AW~lj^#a(_YQeCnQ)}5+8K0|tusSI+Yj{UAYAD-eRHBYuF*1@ zTzsFS*BI!SQ`-@Zc=p(D2*)fs-$I9vF`#H|Od;pkJtxYiZu_0pYU6Jh=(*Ik%hR`g z^~f7FY!s!*mM>8}8EYX|6RCLgSyjC8e5ZvVh0#u~e{APj1? znYuj`>C{g%3cD-&GXySLa6rM`O^uG_NtK=nGX8(g?VMt@jB%gVQx!6v-DFF^;ze>L z`0}R0U0WEABr*{mf@MRcAPf?=hXg`k*BK`Py6})o9E^7vr7kfzseqoSD!rxyt3qGX zd*5=tPB$FtKLENCCU^ayCFt6VL)Tf^LXaxp$QE4){bZ;UbJ!OS?p)Q1v}K z$IY4j)Kv`>xEMRsqZ2ujRlW^~79Tr%t29U5Kyl+Vfxn*F>ekLfrU5|q;68m|IOjcv zA@rfwbk9P@t1bvIBU^jlYxr_MVB&r8c3P*FM~VG;EARC2FLV+SHmxblD&5SCgIZ?H z$5%Df63v9^IR#ykgz_0mG&2&}^N-iXUrM_1k?~+u4*K3Po=(1*C{)}u#uPn@(SeNI zv%6=i$2%)$&jAbl7FK{6d0c2F_DI>AMUL}+ykxtnZGx>u74b3SYxL3u0rsvJd>;g^ z2h|?4lyQewfSw;7i)#p3Y&$e9y6W3_c9Lr)>~M?c7tBq;CUCGo@X4u!Q=QFXi7e(I zm$1WvcL8Bn;j0|{%yZ29;he3qhM~p!1ffU{zjjNE_~SdTRU5ShM6aZ9X-5k48VQT1 z*Mwfxb{4V{RTL!&ru3Z0BJsyiD%ScH!r6e0$l+vVgVZykT>3f!nrh=$5IS`#eky+I z1xAO8-G|Le@6~6T>ytDBbpoZ4DKO$Xai*s{r7X!MZIKutqMtfO^dZ_vWsBJe2J8G; zxL3I>kGqFDQWo~C|S0AJRdc^VxoDb3h znKCJ{%CHJsjJ0<U=1mv6P0W-Q>B>b93>lR@=C}BoHzDN%)`!vE3@#PP;;zTYH8jELY}X8_iJ)Vu&RM|}#Yoa^vUw2o`j<{4pG}VDsthLSo$*B3H{84P zr{32~al-dSZlPGKou5oPAaU6*4f{A}H$G;3bniXUv);2C1h;*n8{(ohlW*6wvN>+vz=;nYKGG! z$$krV*A7>1*X0TO>T>6|L?P?JubAEsF(33r-X$+ip#raco9NpNUnUi77VP@G_h$Ba zH!)woZkCQgS&t7?ytgW+^sVKtJ#V8^H4Fwm}Mlh}92W z+ox8t*e;Iy#RQhli|uD`J{vHPG0z!&HyZscU(P8&FJNjuWGM4Q{|QOu``>SF#BHW$ zRKIl4B`5WBKe&&`_6b^3>x@{9jpmIye~qA2U-R)*W<^!SWN#?3(t5QHU-Fi;yJtcy zbErUAT?ot!BrQko$B&+D$hvE@6y!Ajbj6J{f4zGD?o9cerUQXr56mA$CSL-R7{LJ`VXftrQl;OqMk%Z-(yY?AmQ~CCxAC zZ|kRJ$1D5XWZ7Hu6?LI{y)()c*@e8+mw8TTW{$b>`M~xA&m-|L0nRnt+QqG*$s) zhrmGP6%h7VEE+B+kC2m*Rgi TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 2b7e30a494..3311a17d9a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -314,11 +314,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -564,7 +567,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/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0a03a0f7de..37d61ca176 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -752,7 +752,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?, @@ -1206,11 +1206,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -1242,7 +1245,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) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index a740794c22..936c2e034f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -146,11 +146,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -191,7 +194,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/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index c2cf930192..234d060fed 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -293,11 +293,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -316,7 +319,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 87c3688d60..32a60cdaad 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -250,11 +250,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -279,7 +282,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/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 192883b28f..f82c61b85a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -176,11 +176,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -241,7 +244,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..15ce63a598 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,8 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + dateReplies = Int(attribute.count) } } @@ -226,7 +229,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..a5f4b74f8e 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1024,11 +1024,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -1069,7 +1072,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 } diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index c7ec00047c..626de1c9df 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,8 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -100,7 +103,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..c50b3ad5ad 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -337,11 +337,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -360,7 +363,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)? diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index b36dc9ba26..15b83e1a01 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -109,11 +109,14 @@ 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 { + dateReplies = Int(attribute.count) } } @@ -154,7 +157,7 @@ class ChatMessageTextBubbleContentNode: 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 } From 3f51c89888f3121aa40e0e3dd13025b56e7ce925 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 25 Aug 2020 17:37:55 +0100 Subject: [PATCH 2/5] Merge master --- build-system/bazel-rules/apple_support | 2 +- build-system/bazel-rules/rules_apple | 2 +- build-system/bazel-rules/rules_swift | 2 +- build-system/tulsi | 2 +- submodules/TgVoipWebrtc/tgcalls | 2 +- third-party/libvpx/libvpx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build-system/bazel-rules/apple_support b/build-system/bazel-rules/apple_support index 501b4afb27..b8755bd288 160000 --- a/build-system/bazel-rules/apple_support +++ b/build-system/bazel-rules/apple_support @@ -1 +1 @@ -Subproject commit 501b4afb27745c4813a88ffa28acd901408014e4 +Subproject commit b8755bd2884d6bf651827c30e00bd0ea318e41a2 diff --git a/build-system/bazel-rules/rules_apple b/build-system/bazel-rules/rules_apple index 748e7e2b1e..d6f9a87d70 160000 --- a/build-system/bazel-rules/rules_apple +++ b/build-system/bazel-rules/rules_apple @@ -1 +1 @@ -Subproject commit 748e7e2b1ee5e8976ba873394cbd5e32b61818c7 +Subproject commit d6f9a87d70781e92b95b9344c7d21d921ccc3ae2 diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift index 6408d85da7..44e8c29afe 160000 --- a/build-system/bazel-rules/rules_swift +++ b/build-system/bazel-rules/rules_swift @@ -1 +1 @@ -Subproject commit 6408d85da799ec2af053c4e2883dce3ce6d30f08 +Subproject commit 44e8c29afe3baa7f5fc434f4a7e83f7ac786644e diff --git a/build-system/tulsi b/build-system/tulsi index ee185c4c20..e890fb6c88 160000 --- a/build-system/tulsi +++ b/build-system/tulsi @@ -1 +1 @@ -Subproject commit ee185c4c20ea4384bc3cbf8ccd8705c904154abb +Subproject commit e890fb6c88846454c4f69abd8d265a302c0e80e4 diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index d8b106c94e..b906b5a955 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit d8b106c94e5a5e20020ad862d835e921d4f102d9 +Subproject commit b906b5a955f1e6e195cb91c4513d4cb7b3620ea6 diff --git a/third-party/libvpx/libvpx b/third-party/libvpx/libvpx index c413c8f18e..2d20a42ab6 160000 --- a/third-party/libvpx/libvpx +++ b/third-party/libvpx/libvpx @@ -1 +1 @@ -Subproject commit c413c8f18eb1932b100850505031980e27160d5f +Subproject commit 2d20a42ab60f8fed37612361f2ea776d0bc7ca1a From 0e03c5e44646138dd63f88e9758f2317eba0f174 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 28 Aug 2020 17:32:21 +0100 Subject: [PATCH 3/5] Comments update --- .../Sources/AccountContext.swift | 10 +- .../Sources/StoredMessageFromSearchPeer.swift | 4 +- .../Sources/ChatListControllerNode.swift | 2 +- .../Sources/LegacyChatImport.swift | 2 +- .../Sources/LiveLocationManager.swift | 2 +- .../AdditionalMessageHistoryViewData.swift | 2 + .../Postbox/Sources/ChatListViewState.swift | 2 +- submodules/Postbox/Sources/ChatLocation.swift | 10 +- .../Sources/GlobalMessageTagsView.swift | 4 +- .../Postbox/Sources/IntermediateMessage.swift | 12 +- submodules/Postbox/Sources/Message.swift | 48 +- .../Sources/MessageHistoryHolesView.swift | 37 + .../Postbox/Sources/MessageHistoryTable.swift | 87 +- .../Postbox/Sources/MessageHistoryView.swift | 196 ++- .../Sources/MessageHistoryViewState.swift | 196 ++- .../Sources/MessageOfInterestHolesView.swift | 9 +- submodules/Postbox/Sources/Postbox.swift | 160 ++- submodules/Postbox/Sources/ViewTracker.swift | 20 +- .../BubbleSettingsController.swift | 10 +- .../ForwardPrivacyChatPreviewItem.swift | 2 +- .../TextSizeSelectionController.swift | 22 +- .../ThemeAccentColorControllerNode.swift | 24 +- .../Themes/ThemePreviewControllerNode.swift | 30 +- .../Themes/ThemeSettingsChatPreviewItem.swift | 4 +- .../Sources/Themes/WallpaperGalleryItem.swift | 4 +- .../Sources/ReplyMessageAttribute.swift | 12 +- .../Sources/ReplyThreadMessageAttribute.swift | 10 +- submodules/TelegramApi/Sources/Api0.swift | 7 +- submodules/TelegramApi/Sources/Api1.swift | 124 +- submodules/TelegramApi/Sources/Api3.swift | 55 +- .../Sources/TelegramBaseController.swift | 2 +- .../Sources/AccountStateManagementUtils.swift | 42 +- .../Sources/AccountViewTracker.swift | 83 +- .../ApplyMaxReadIndexInteractively.swift | 4 +- .../Sources/ApplyUpdateMessage.swift | 14 +- .../TelegramCore/Sources/DeleteMessages.swift | 16 +- .../Sources/DeleteMessagesInteractively.swift | 2 +- .../TelegramCore/Sources/EnqueueMessage.swift | 23 +- .../Sources/HistoryViewStateValidation.swift | 10 +- ...InstallInteractiveReadMessagesAction.swift | 2 +- .../ManagedAutoremoveMessageOperations.swift | 2 +- ...anagedConsumePersonalMessagesActions.swift | 4 +- .../Sources/ManagedMessageHistoryHoles.swift | 13 +- .../ManagedSecretChatOutgoingOperations.swift | 4 +- ...kAllUnseenPersonalMessagesOperations.swift | 2 +- ...essageContentAsConsumedInteractively.swift | 4 +- .../Sources/MessageReactions.swift | 4 +- .../TelegramCore/Sources/MessageUtils.swift | 2 +- .../Sources/PendingMessageManager.swift | 18 +- .../PendingMessageUploadedContent.swift | 4 +- ...ecretChatIncomingDecryptedOperations.swift | 24 +- .../Sources/ReplyThreadHistory.swift | 140 +- .../Sources/RequestEditMessage.swift | 2 +- .../Sources/StoreMessage_Telegram.swift | 24 +- .../Sources/UpdateMessageMedia.swift | 59 +- .../Sources/UpdateMessageService.swift | 4 +- .../Resources/PresentationResourceKey.swift | 3 + .../Resources/PresentationResourcesChat.swift | 16 + .../Replies.imageset/Contents.json | 12 + .../Replies.imageset/ic_viewreplies.pdf | Bin 0 -> 4676 bytes .../BubbleComments.imageset/Contents.json | 12 + .../ic_leaveacomment (1).pdf | Bin 0 -> 4385 bytes .../TelegramUI/Sources/AccountContext.swift | 9 + .../TelegramUI/Sources/ChatController.swift | 1230 +++++++++-------- .../Sources/ChatControllerInteraction.swift | 76 +- .../Sources/ChatControllerNode.swift | 12 +- .../TelegramUI/Sources/ChatEmptyNode.swift | 16 +- .../Sources/ChatHistoryEntriesForView.swift | 1 + .../Sources/ChatHistoryGridNode.swift | 2 +- .../Sources/ChatHistoryListNode.swift | 7 +- .../Sources/ChatHistoryViewForLocation.swift | 39 +- .../Sources/ChatInfoTitlePanelNode.swift | 2 + .../ChatInterfaceStateContextMenus.swift | 152 +- .../ChatInterfaceStateNavigationButtons.swift | 3 + .../Sources/ChatMediaInputNode.swift | 2 +- .../ChatMessageAnimatedStickerItemNode.swift | 26 +- .../ChatMessageAttachedContentNode.swift | 4 +- .../ChatMessageBubbleContentNode.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 180 +-- .../ChatMessageCommentFooterContentNode.swift | 260 ++++ .../ChatMessageContactBubbleContentNode.swift | 4 +- .../ChatMessageInstantVideoItemNode.swift | 40 +- .../ChatMessageInteractiveFileNode.swift | 4 +- ...atMessageInteractiveInstantVideoNode.swift | 4 +- .../TelegramUI/Sources/ChatMessageItem.swift | 43 +- .../ChatMessageMapBubbleContentNode.swift | 6 +- .../ChatMessageMediaBubbleContentNode.swift | 4 +- .../ChatMessagePollBubbleContentNode.swift | 35 +- ...atMessageRestrictedBubbleContentNode.swift | 4 +- .../Sources/ChatMessageStickerItemNode.swift | 26 +- .../ChatMessageTextBubbleContentNode.swift | 38 +- .../ChatPanelInterfaceInteraction.swift | 3 + .../ChatPinnedMessageTitlePanelNode.swift | 23 +- .../Sources/ChatRecentActionsController.swift | 2 +- .../ChatRecentActionsControllerNode.swift | 1 + .../ChatRecentActionsHistoryTransition.swift | 60 +- .../ChatSearchNavigationContentNode.swift | 9 +- .../Sources/ChatTextInputPanelNode.swift | 3 + .../TelegramUI/Sources/ChatTitleView.swift | 18 +- .../Sources/DrawingStickersScreen.swift | 1 + .../Sources/NavigateToChatController.swift | 4 + .../Sources/OverlayPlayerControllerNode.swift | 1 + .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 3 +- .../PeerMediaCollectionController.swift | 3 +- .../PreparedChatHistoryViewTransition.swift | 1 + .../Sources/SharedAccountContext.swift | 5 +- ...annelMemberCategoriesContextsManager.swift | 29 + 108 files changed, 2678 insertions(+), 1379 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf create mode 100644 submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 4bc3fe02d3..a77f51cb79 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(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, 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? @@ -659,4 +665,6 @@ public protocol AccountContext: class { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? + + func chatLocationInput(for location: ChatLocation) -> ChatLocationInput } 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 d336726443..169d57ed6f 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..d5fe03d6c8 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 { @@ -374,10 +375,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 +980,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 +1005,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 +1167,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 } @@ -1331,7 +1338,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()) @@ -1542,6 +1549,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 +1574,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 +1721,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 +1799,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 +1839,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 +1876,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 +1901,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 +2106,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 +2219,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 +2368,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 +2703,50 @@ 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 { + 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/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 895cdbfe4f..170592f0f2 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,15 +243,42 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } -public protocol MessageHistoryViewExternalInput: class { - func getNamespaces() -> [MessageId.Namespace] - func getMessageIds(namespace: MessageId.Namespace) -> [MessageId] - func getHoleIndices(namespace: MessageId.Namespace) -> IndexSet +public final class MessageHistoryViewExternalInput: Equatable { + public let peerId: PeerId + public let threadId: Int64 + public let holes: [MessageId.Namespace: IndexSet] + + public init( + peerId: PeerId, + threadId: Int64, + holes: [MessageId.Namespace: IndexSet] + ) { + self.peerId = peerId + self.threadId = threadId + 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 { @@ -294,14 +328,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 { @@ -361,12 +395,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 @@ -375,52 +411,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): @@ -453,13 +504,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) { @@ -497,16 +557,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): @@ -585,6 +647,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) @@ -673,19 +779,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 } } @@ -713,8 +821,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 { @@ -724,7 +832,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 } diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index f53136c21e..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 @@ -784,7 +837,6 @@ final class HistoryViewLoadedState { 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 @@ -1230,7 +1311,6 @@ final class HistoryViewLoadedState { } private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { - var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] var peerIds: [PeerId] = [] switch locations { case let .single(peerId): @@ -1240,37 +1320,59 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, ta 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: MessageHistoryViewInput, 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) } } @@ -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 9efbe3090e..79ebc808dc 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -47,7 +47,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { 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 @@ -130,7 +130,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { 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) { @@ -158,7 +161,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { 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 a9d559ebbd..fad732e7de 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1475,7 +1475,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 } @@ -1674,7 +1674,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 { @@ -1690,7 +1690,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)) } } @@ -2341,7 +2341,19 @@ public final class Postbox { } } - func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewInput { + 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): @@ -2349,85 +2361,100 @@ public final class Postbox { 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: 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) + 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 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 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) + } + } + } + + 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: 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) { @@ -2461,6 +2488,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: @@ -2499,6 +2529,8 @@ public final class Postbox { if let readState = self.readStateTable.getCombinedState(peerId) { transientReadStates = .peer([peerId: readState]) } + case .external: + transientReadStates = nil } if let fixedCombinedReadStates = fixedCombinedReadStates { @@ -2525,6 +2557,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)) @@ -3186,7 +3220,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..2b866c7ab9 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) @@ -480,6 +485,19 @@ final class ViewTracker { subscriber.putNext(MessageHistoryHolesView(self.messageHistoryHolesView)) } } + + var firstExternalHolesAndTags = Set() + for (view, _) in self.messageHistoryViews.copyItems() { + /*if let (hole, direction, count) = view.firstExternalHole() { + firstExternalHolesAndTags.insert(MessageHistoryHolesViewEntry(hole: hole, direction: direction, count: count)) + }*/ + } + + if self.messageHistoryHolesView.update(firstHolesAndTags) { + for subscriber in self.messageHistoryHolesViewSubscribers.copyItems() { + subscriber.putNext(MessageHistoryHolesView(self.messageHistoryHolesView)) + } + } } private func unsentViewUpdated() { diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 0925787231..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, threadMessageId: nil)], 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/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 e3089a6248..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, threadMessageId: nil)], 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 32b8f2268b..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, threadMessageId: nil)], 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 9bca619021..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, threadMessageId: nil)], 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 975a874880..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, threadMessageId: nil)] : [], 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 5ab64d24fe..308c0ad9c1 100644 --- a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift @@ -37,18 +37,10 @@ public class ReplyMessageAttribute: MessageAttribute { } } -public extension ReplyMessageAttribute { - var effectiveReplyThreadMessageId: MessageId { - return self.threadMessageId ?? self.messageId - } -} - public extension Message { var effectiveReplyThreadMessageId: MessageId? { - for attribute in self.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - return attribute.effectiveReplyThreadMessageId - } + 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 index c7cc4f4da0..67ece5888b 100644 --- a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift @@ -3,16 +3,24 @@ import Postbox public class ReplyThreadMessageAttribute: MessageAttribute { public let count: Int32 + public let latestUsers: [PeerId] - public init(count: Int32) { + public var associatedPeerIds: [PeerId] { + return self.latestUsers + } + + public init(count: Int32, latestUsers: [PeerId]) { self.count = count + self.latestUsers = latestUsers } required public init(decoder: PostboxDecoder) { self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.latestUsers = decoder.decodeInt64ArrayForKey("u").map(PeerId.init) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.count, forKey: "c") + encoder.encodeInt64Array(self.latestUsers.map { $0.toInt64() }, forKey: "u") } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index ae34427ed6..ec775fdf84 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -388,7 +388,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } - dict[23453020] = { return Api.MessageViews.parse_messageViews($0) } + dict[-119526594] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) } dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($0) } @@ -541,6 +541,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) } @@ -602,7 +603,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1820043071] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } dict[-1642487306] = { return Api.Message.parse_messageService($0) } - dict[2119777911] = { return Api.Message.parse_message($0) } + dict[-739735064] = { return Api.Message.parse_message($0) } dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } @@ -1259,6 +1260,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 a67fd40605..4ae306faca 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1209,6 +1209,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) @@ -11752,26 +11802,32 @@ public extension Api { } public enum MessageViews: TypeConstructorDescription { - case messageViews(views: Int32, forwards: Int32, replies: Int32, repliesPts: Int32) + case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Int32?, repliesPts: Int32?, recentRepliers: [Int32]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageViews(let views, let forwards, let replies, let repliesPts): + case .messageViews(let flags, let views, let forwards, let replies, let repliesPts, let recentRepliers): if boxed { - buffer.appendInt32(23453020) + buffer.appendInt32(-119526594) } - serializeInt32(views, buffer: buffer, boxed: false) - serializeInt32(forwards, buffer: buffer, boxed: false) - serializeInt32(replies, buffer: buffer, boxed: false) - serializeInt32(repliesPts, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(replies!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(repliesPts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRepliers!.count)) + for item in recentRepliers! { + serializeInt32(item, buffer: buffer, boxed: false) + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageViews(let views, let forwards, let replies, let repliesPts): - return ("messageViews", [("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts)]) + case .messageViews(let flags, let views, let forwards, let replies, let repliesPts, let recentRepliers): + return ("messageViews", [("flags", flags), ("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers)]) } } @@ -11779,17 +11835,25 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: Int32? - _3 = reader.readInt32() + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } var _4: Int32? - _4 = reader.readInt32() + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } + var _6: [Int32]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageViews.messageViews(views: _1!, forwards: _2!, replies: _3!, repliesPts: _4!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4, repliesPts: _5, recentRepliers: _6) } else { return nil @@ -17170,7 +17234,7 @@ public extension Api { public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) - case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, replyToTopId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Int32?, repliesPts: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) + case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, replyToTopId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Int32?, recentRepliers: [Int32]?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17192,9 +17256,9 @@ public extension Api { serializeInt32(date, buffer: buffer, boxed: false) action.serialize(buffer, true) break - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let repliesPts, let editDate, let postAuthor, let groupedId, let restrictionReason): + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let recentRepliers, let editDate, let postAuthor, let groupedId, let restrictionReason): if boxed { - buffer.appendInt32(2119777911) + buffer.appendInt32(-739735064) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -17216,7 +17280,11 @@ public extension Api { if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 23) != 0 {serializeInt32(replies!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {serializeInt32(repliesPts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 25) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRepliers!.count)) + for item in recentRepliers! { + serializeInt32(item, buffer: buffer, boxed: false) + }} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} @@ -17235,8 +17303,8 @@ public extension Api { return ("messageEmpty", [("id", id)]) case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let repliesPts, let editDate, let postAuthor, let groupedId, let restrictionReason): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("replyToTopId", replyToTopId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let recentRepliers, let editDate, let postAuthor, let groupedId, let restrictionReason): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("replyToTopId", replyToTopId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("recentRepliers", recentRepliers), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) } } @@ -17327,8 +17395,10 @@ public extension Api { if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } var _16: Int32? if Int(_1!) & Int(1 << 23) != 0 {_16 = reader.readInt32() } - var _17: Int32? - if Int(_1!) & Int(1 << 23) != 0 {_17 = reader.readInt32() } + var _17: [Int32]? + if Int(_1!) & Int(1 << 25) != 0 {if let _ = reader.readInt32() { + _17 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } var _18: Int32? if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } var _19: String? @@ -17355,13 +17425,13 @@ public extension Api { let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 23) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 23) == 0) || _17 != nil + let _c17 = (Int(_1!) & Int(1 << 25) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil let _c21 = (Int(_1!) & Int(1 << 22) == 0) || _21 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, replyToTopId: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, repliesPts: _17, editDate: _18, postAuthor: _19, groupedId: _20, restrictionReason: _21) + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, replyToTopId: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, recentRepliers: _17, editDate: _18, postAuthor: _19, groupedId: _20, restrictionReason: _21) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 27251ed127..6758a82215 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -3629,26 +3629,6 @@ public extension Api { }) } - public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageViews]>) { - let buffer = Buffer() - buffer.appendInt32(-39035462) - 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 getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1824339449) @@ -3743,6 +3723,41 @@ public extension Api { return result }) } + + public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(8956627) + 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.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 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 struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index b83bf9492b..49fe371681 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/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index e193a30c6b..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,12 +2274,24 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var topUpperHistoryBlockMessages: [PeerIdAndMessageNamespace: MessageId.Id] = [:] - var messageThreadStatsDifferences: [MessageId: Int] = [:] - func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int) { + 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] { - messageThreadStatsDifferences[threadMessageId] = value + add - remove + value.count += add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } } else { - messageThreadStatsDifferences[threadMessageId] = add - remove + let value = MessageThreadStatsRecord() + messageThreadStatsDifferences[threadMessageId] = value + value.count = add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } } } @@ -2289,14 +2301,12 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP if case .UpperHistoryBlock = location { for message in messages { if case let .Id(id) = message.id { - findAttribute: for attribute in message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - if id.peerId.namespace == Namespaces.Peer.CloudChannel { - if !transaction.messageExists(id: id) { - addMessageThreadStatsDifference(threadMessageId: attribute.effectiveReplyThreadMessageId, add: 1, remove: 0) - } + 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) } - break findAttribute } } } @@ -2410,7 +2420,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } case let .DeleteMessages(ids): deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in - addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove) + addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil) }) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { @@ -2802,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 @@ -2817,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) @@ -2936,7 +2946,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } for (threadMessageId, difference) in messageThreadStatsDifferences { - updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference) + updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference.count, addedMessagePeers: difference.peers) } if !peerActivityTimestamps.isEmpty { diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index db01fd8b92..a2cd1a961a 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,35 +597,52 @@ 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, replies, _) = 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 { + |> 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, _, recentRepliers) = viewCounts[i] { + transaction.updateMessage(messageIds[i], update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + var foundReplies = false + let recentRepliersPeerIds: [PeerId]? + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } + } else { + recentRepliersPeerIds = nil + } + 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 { + } + } else if let _ = attributes[j] as? ForwardCountMessageAttribute { + if let forwards = forwards { attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) - } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { - attributes[j] = ReplyThreadMessageAttribute(count: replies) + } + } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { + foundReplies = true + if let replies = replies { + attributes[j] = ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? []) } } - 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)) - }) - } + } + if !foundReplies, let replies = replies { + attributes.append(ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? [])) + } + 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 { @@ -941,7 +964,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()) @@ -1074,7 +1097,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) @@ -1103,6 +1126,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) + } } } } @@ -1149,7 +1176,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 @@ -1179,7 +1206,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) @@ -1188,7 +1215,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) @@ -1197,7 +1224,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 1ec9818c25..22f7080114 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? @@ -224,18 +224,16 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } } - let updatedMessageValue = 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 { - for attribute in updatedMessage.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - updateMessageThreadStats(transaction: transaction, threadMessageId: attribute.effectiveReplyThreadMessageId, difference: 1) - break - } + if let threadId = updatedMessage.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId) + updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [accountPeerId]) } } } @@ -375,7 +373,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/DeleteMessages.swift b/submodules/TelegramCore/Sources/DeleteMessages.swift index f42329c25a..c8c06b4de4 100644 --- a/submodules/TelegramCore/Sources/DeleteMessages.swift +++ b/submodules/TelegramCore/Sources/DeleteMessages.swift @@ -40,16 +40,14 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M for id in ids { if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud { if let message = transaction.getMessage(id) { - findAttribute: for attribute in message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - if id.peerId.namespace == Namespaces.Peer.CloudChannel { - if let manualAddMessageThreadStatsDifference = manualAddMessageThreadStatsDifference { - manualAddMessageThreadStatsDifference(attribute.messageId, 0, 1) - } else { - updateMessageThreadStats(transaction: transaction, threadMessageId: attribute.effectiveReplyThreadMessageId, difference: -1) - } + 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: []) } - break findAttribute } } } 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 b7dab93053..4f23a58f68 100644 --- a/submodules/TelegramCore/Sources/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/EnqueueMessage.swift @@ -412,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] { @@ -527,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) + } } } @@ -572,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 da433663bf..f7374880b9 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/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..e31f52c341 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 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 9b4d821ae6..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))]) } } } @@ -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: @@ -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: @@ -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 index 89c331d2bc..57eff8064e 100644 --- a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -2,30 +2,164 @@ 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: (MessageHistoryExternalHolesViewEntry, Disposable)? struct NamespaceState: Equatable { var sortedMessageIds: [MessageId] var holeIndices: IndexSet } - init(queue: Queue, account: Account) { + struct State: Equatable { + let messageId: MessageId + let namespaces: [MessageId.Namespace] + let namespaceStates: [MessageId.Namespace: NamespaceState] + } + + let state = Promise() + private var stateValue: State { + didSet { + self.state.set(.single(self.stateValue)) + } + } + + init(queue: Queue, account: Account, messageId: MessageId) { self.queue = queue self.account = account + self.messageId = messageId + + self.stateValue = State(messageId: self.messageId, namespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], namespaceStates: [:]) + self.state.set(.single(self.stateValue)) + + /*self.setCurrentHole(hole: MessageHistoryExternalHolesViewEntry( + hole: .peer(MessageHistoryViewPeerHole(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, threadId: makeMessageThreadId(self.messageId))), + direction: .range(start: MessageId(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, id: Int32.max - 1), end: MessageId(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, id: 1)), + count: 100 + ))*/ + } + + func setCurrentHole(hole: MessageHistoryExternalHolesViewEntry?) { + if self.currentHole?.0 != hole { + self.currentHole?.1.dispose() + if let hole = hole { + self.currentHole = (hole, self.fetchHole(hole: hole).start()) + } else { + self.currentHole = nil + } + } + } + + private func fetchHole(hole: MessageHistoryExternalHolesViewEntry) -> Signal { + let messageId = self.messageId + let account = self.account + return self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .complete() + } + return account.network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: messageId.id, offsetId: Int32.max - 1, addOffset: 0, limit: Int32(hole.count), maxId: Int32.max - 1, minId: 1, hash: 0)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .complete() + } + return account.postbox.transaction { transaction -> Void in + switch result { + case .messages(let messages, let chats, let users), .messagesSlice(_, _, _, let messages, let chats, let users), .channelMessages(_, _, _, let messages, let chats, let users): + break + case .messagesNotModified: + break + } + } + |> ignoreValues + } + } } } -class ReplyThreadHistoryContext { +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), holes: [:] + )) + }) + disposable.set(stateDisposable) + } + + return disposable + } + } + public init(account: Account, peerId: PeerId, threadMessageId: MessageId) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return ReplyThreadHistoryContextImpl(queue: queue, account: account) + return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId) }) } } + +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 -> MessageIndex? in + switch result { + case .messages(let messages, let chats, let users), .messagesSlice(_, _, _, let messages, let chats, let users), .channelMessages(_, _, _, let messages, let chats, let users): + guard let message = messages.first else { + return nil + } + guard let parsedMessage = StoreMessage(apiMessage: message) else { + return nil + } + return parsedMessage.index + case .messagesNotModified: + return nil + } + } + } + } +} 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/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 2bf27e1f18..929d0af677 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -399,7 +399,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, replyToTopId, date, message, media, replyMarkup, entities, views, forwards, replies, _, editDate, postAuthor, groupingId, restrictionReason): + case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, replyToTopId, date, message, media, replyMarkup, entities, views, forwards, replies, recentRepliers, editDate, postAuthor, groupingId, restrictionReason): let peerId: PeerId var authorId: PeerId? switch toId { @@ -514,10 +514,17 @@ extension StoreMessage { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil)) } + var threadId: Int64? if let replyToMsgId = replyToMsgId { var threadMessageId: MessageId? if let replyToTopId = replyToTopId { - threadMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: 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)) } @@ -569,7 +576,14 @@ extension StoreMessage { }*/ if let replies = replies { - attributes.append(ReplyThreadMessageAttribute(count: replies)) + let recentRepliersPeerIds: [PeerId]? + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } + } else { + recentRepliersPeerIds = nil + } + + attributes.append(ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? [])) } if let restrictionReason = restrictionReason { @@ -612,7 +626,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): @@ -681,7 +695,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 121f228cf0..5b9088c408 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -24,12 +24,52 @@ 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) { +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) @@ -37,14 +77,21 @@ func updateMessageThreadStats(transaction: Transaction, threadMessageId: Message var updated = false loop: for j in 0 ..< attributes.count { if let attribute = attributes[j] as? ReplyThreadMessageAttribute { - attributes[j] = ReplyThreadMessageAttribute(count: max(0, attribute.count + countDifference)) + let count = max(0, attribute.count + countDifference) + attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0)) updated = true - break loop + } else if let attribute = attributes[j] as? SourceReferenceMessageAttribute { + channelThreadMessageId = attribute.messageId } } if !updated { - attributes.append(ReplyThreadMessageAttribute(count: max(0, countDifference))) + let count = max(0, countDifference) + attributes.append(ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: [], added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0))) } - 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)) }) + + if let channelThreadMessageId = channelThreadMessageId { + updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: channelThreadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: true) + } } diff --git a/submodules/TelegramCore/Sources/UpdateMessageService.swift b/submodules/TelegramCore/Sources/UpdateMessageService.swift index b79e3b5e56..fcb62ef9ee 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, repliesPts: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, recentRepliers: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -75,7 +75,7 @@ class UpdateMessageService: NSObject, MTMessageService { generatedToId = Api.Peer.peerUser(userId: self.peerId.id) } - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, repliesPts: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, recentRepliers: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { 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 0000000000000000000000000000000000000000..db1722552c52859579aa1c55bfd2daacf73450f3 GIT binary patch literal 4676 zcmai&2UHW;8pkP7ASj~JMHvx-C_+L)iPC!jse*)RfB>Nd61q~Q2n6XOU5ZMPCa83z zND-Du7Zng8AYIB*MPG2;x9;2b&YLr5=AQ3<_kZr(Z{~k~a|QKO)Gon9;b6fw${JYj$j!XKujC!YVY9yNKiutfS4N2*#k?U-kmWXSQV@d-WChU$$^O; z1T4k{>_cmjtXRYgJEF4X4=$s37twmU=&2KD!~$hKvCR}37}%LucXQ^X9n8zKP9Rxx zxrt)7W0u=lWaG1Akvo4!ZaAi`-8aMSi?mgu(U~p6aE|PT&*==Kp~Q7_nWzyih$Ozv z(Z_p1Rhmumdd213x#^yQPfqQt8Y0RuYhIT|i0o21BYhV6Dei`MFPB6@2nH=|HNgud zJtI*9o;o!&MRCK|&%jfoKPbpp(^g3s#&ytwv>+a(RXiZPYQ8A<73LTY5W_;oKw-v~ zTu1J zFUXKxUNV)@#9udC5kh}--%N}0%{U}9`;4RsW$NPUx6j@f*4};Fkc4ej7&J{!)!=pR zBy=m1*GI=oHRYU4=bo8Gr6=f^M)E2#NIb|gQPjV1Jc(znB95Cevx6h-BuKsg1+4j6 z%3SEhqA-W4fa5GlbG=`s8Vo3kP$(0#_-Cow(|^t7bcK1lfg0TpQ0S7z>`cADtYk=RMrYbmAQp ze1dd8QF!RGP%_b_-nOrnHK02oNn6)b5#_}MQ)=z!>#YvzOji?){1ZK?A{d| z*LvTyrM}a1t8e}K;;=dThHD5O@Af|`0|8XT?T|EH!AEse|CE`5^HdrDc`A0w* z@9IJACjtjXV{l*~KlKj!|1=&w0^Y_D>j9WiMOD-Q3qVX6?~ErHx?yavz=1O<6JdbV zPk~=Fq<_))X;7y#O&4tQXD(YoM<5|9vgx1iy_c8}scD>k+0kR=xIS zeHB&87|Drgr9@(VJZ--NJuy1_Vr0~Q#ebxhM(=9~sKvvCHVBbp!nVB7Y<}WociR>n z7b`7`3GK^CJ3Em2whGuN22{7E3?eyC-T7@i8C3jlZ|MqTn;NxCZFi805ofdXsX6D*3X1HTPC!9IwHgzy}0O1q|J5`z4%#bFFOSP zx+xsFmD;_$XME)mr+n}^6gwQNN_q77k(NqN&k>dsvhDsB>-VXbNZfB-ts)uOFF9Ak zJ{#$OPGSm;bf^oH2{wrnwFQR;4owDfNeVG=2V^?2Ie1#p_=h3&s!*zXNU zCq&ZiusP`Z%cE}0M}pK9z~RkWP}=ce9An5AR`y)QqBxHHW2s8Rtfx#FeU%mUq}k|S zDM!Y!CMfK@ffR6ExLuT^tS7s1&V#3x(c<>*1BN#M;_yDLf9Uc1bOV^9Z)u(fjh0{G zWmIqJ{;1-^p25KR^8KV{HHRF_ix$qwQ(tIA!mL^n85KGi>HE}77*DOLD8-ow&>lbQ z6wjYeSE!m6$C7`lCPpLPeVYDoL=NUY7e_nyR0NtdX>E)dw$_9W-M6`Ktj#$iX~zl= z-U)eVJyh|?MCggU7n9`i^q5O+#y4Vbn3bng(7(kjUg2fwc**^V*Dau$%Us-5q7?Mv z*a)dEXrcA!BxKd6{=zuhO31Nh;uq^%XB)wRyn$yXV)@#egyNV?K~5pZ&fWtAoCU72 z@-WUa?uQ<4LFfk;=z8#nv--A~TMJj*eXCfH;)UoWvZ2BSI1L1ZQ>%inp&a=wAhM8# zvx!}oZPf`}Xa!5%Qh{v1O7K{Myk63I2%D}Zud33h4pOs5!B@do`Jutl0@p#4qWiU( zrn(I({+j+`>WT1_^^~culEmV8r{sl{n}WJYBPre~R-)NLR%Zh>zdia|O{d?i&}X#4CNl`h?(w7bkQJ73RwO1s|T}Q5tyY+h1LE zwd(Ra>^tabb*cQlfyB928+@M|Hkh!2STo9b3NM9+Qu*58a0~RtHr#;wAt{kmM~YoX z%+!#l$nPb~aF*Zeg_CzKxL&xG%%5zROq(2Aq-}&BVjIdVaw*asggQbTF^;2-dqbkd zG33W&?<{hr72^B!*NFM@+DO0ZUB};B=Vj*;dD3_qdANCucv2AU65MI7X-jfyyqGnQx zFU(&UdMoK_iJ6F5mFchK$mwU7Wal@jH<<;#e)$G?cOzOcy}2MKDyIpPg~^+=Iad^4 z+;<_*H1A5gw9Rw(s8zWsGe~)4CMp3H({O zL~Zy1vQ?(@R-SMA&P8U~Xp`tnjxi1^ktGqsOA8`=Xkqjr6PK#_^4->VZD~#xHvS#q zj#D<3y^qFbo|P0{n(%5`^1qU1Q9i~|r&CAXWZC53gY3Z=WFvec8d$d8dU&0$3-up5 zzVLanx_6Z{#UON3sA2&1cp?+sE8KguH;?%U=NHbwOI(CL_sMErLcFN0D9OslEoac^ zee#s)!fwyik(gPpHkY=sx3Z)A)q59@X&sAKkM2l%oH)xvX12VOQg%K5dfAJFE$bD> zmB(_Id`SAS{&YLUFxq?ht#jC-c(Id)k-lDL03+zkxWUzBUuimv3N zX4@~$#;jUwW533OQbM1G_Nk<_Swz~PN>S{nDLP%nXCFPETCD|77be9e4JZj>qRKB?z-5IR#99$~Q03a!xS?gEler3g@!IE!XuG=mcfV=>u0aw=7rc#T zu5^4lZm*8bey!hgd}d=hZQ8Z_bk};i$YjnIV znrkP2Z(N9V0bDdjzjUj0$6!xQxJHc+>OasgZ@l49xiZtPRHLLn*>I`XjK%qtGrRNB zm|bOwOyFW#J(iQxWurPu4zrHipvl+V7@No0tp7%#DM(+<}bG7Rxsn%%A z(Kn?ZElMaoYq@JL>aH&~8e`1dZydwL+QwFi7d&myEOS&JdOV*$AIuY*Hq$VF^Tkr; zyN>rGO%pDG1GF*puNdUcu<_Ohc5dl8l_<0NFNwRUeREECZ{kTz4(Dqq`ck>h*W zGt!aS-T`Y$?P06Yk(?2iuY1VVR!#S3mX?=}cL%4GS+3R)3g0*EeKkrcwl76mT=CEJ zZ&(W7j~PBwmvzr-F#tFBY{jKv&aHC)-c-rm#zX$IyB2$5RU*a*BmcsMD7l6y3E>so*wYVtvq@hHD25(zZtxrvuCr>5kI%6yRDm?9V733 zi|Ol{55&c8W@0OJeea+Ad_bW;pji_3Z-74F*Mk@pa#704N*E&67B~P{1Hj@>CO&}Z zKbiP1#wG${SFyG@j1t}lFoRN4#1Yi<2PAt^$r}cUY2j>%RCqo>bgJ4ffDAiG|GT0x z#slMwxBmm*i9fmhFD!@sGJuNZww^ZBJ%IBr7#m$fz!*y);_$8j3@Qqf5S0MT&ntQ2 zoNWOZQVj-$nF|B@o*06MA3$aJAK35XAxuSg>UMz#b(9K~!@=iOE{j9Op-^dYDVR7E z0T(xgLWQU&HSa3k_8px;J$$#j;Fevh581_z~OL&t+WK( z1}2FRN7^FcNE=%?24N=wm4+dut)X(@|DW>58$|S=*7CHaGj83ALs8OOtH%LVEUJ?>Q)QEmf zL??)7(QDL*cjV@NH}|{Wx8AePI%ltE|M#=@+0Xv3_1lL{9VK@Q#>)?3Yn|JgTh8Bp z{-L!EA^<=ECv#hfs3^dvf^oERwFVG`kS4$f&t7G4>!JjEl;(Yj@gOo`HLIfj>zgmt`_a0>gbfvYz|WW ztlEw6H>ez!{S^ldKkDih-BROW*)<(+L;g=UDEO{LF6_R@^O+k1ULmPFAm7hhT8(^b z(;QF>5$ESDe3UD?e2v-6vDhf=kFAN^;ICsB&cA%k<2L!o{ZbG%S2RJ-PPb$wOHE5z z%ufXAJ4T~4MlU&2pQ0lBx^*sj$TLVdkjxJh0tcmYVKW6e%u`mcSIVFz%f=#27^-?o z;w=S)@9&(^Y)ICQ7Ryc&1y{!ojE@%-*42Q=X^e;prLwdZ=meSnX#Tidg&Qc&v14It zT0A7NLrjKgN6;7TU*_sJI`Ui{T16&O4j?37;#Pb+7ro>F1uL1RisvNX21!)vxL%g`-e^jBehcH$?ukoJWq&yQ zxeKdpFPvDR@TCGoUI1c{mcBD}b1Sw&lZUAF>T0G^Z51{Ht{)%+34AivI~2DDTG{1PYfo>%#;z~5Vb4WyvmwmlNUE|JuCY&hIc6ZbnIhqTcx|Z5a z#PVS*7P2(w9tN2YlW$S0w>4{^WalQL?8w(k5!;h#$L;!4qYKr;qgLy_!?i@}--Ey{ zu6o1)0-1U=Ys<|>7cX_U?tv~-6I1FDH%())VEKI%XyALjSZZp4LR;nT8gGDQ-8fI9 zI=joziGLi6pbYC-B8coDQq+%~eh!qVRr#I)egf{=N#cZu!r*k7|)`g3_d# z_AIbx+K0nsX&BbRw?IT>iLJtKohc&^gsqDr>?Or?570IKQ2)v<4%a4(r%gfJGM)mYiP~z~G z$9vQ#Gfl!ae{{a%d79BgzZvpXN8@WEd2qOOU5IF)UM#OAB-np&+W#_=gOtH9-HyiE z&6LPDL|DB>k+j1}j7a7N39F-oH^_I{kQxT*Xn1Q~8dvTT1NZ*1aL9blD8g}CMT$jh zewQM~(wQxbhar0^*uEKbF_zTzIYc0k7YgDrJI@rre~)AmAPNpppe9Ks;Z*?HNP=HV ziH{ILq+lb&?hkG65e)=-e*oVJLf$)T1issJ{@yu?0C`DBXtOeucrwt2ENFw8HcP4~ zmM(`XReFf}iUFCIjFh?%4as|%@L1}2$%7B<1(&%V7G=t)i|yQSWvnGLet7tt^aCJp z_L$f=_(BS(AANp==ykwog$NUwd`tIdlqc;|Qu?OxX{8!Eamwlz`sphhL_8s;EeT|j z9b_bZa(ZM}Hc`^CdaT43uG_`2h`T>aayBdzopPD3jbSECKWf@s z7d>RI1RYGVNYPcHpGRV;`2!Dva?J-{UiN76FaG0yS5bP-?GMBXiCdk4L25Dl)O(NuW^- zWu@0-VJdJly=@7sPuFwzu(t@zodA z3MCgSKC3EfG)Oa; zUgS`uHUPC{w?*5I+8zz^7Dwar@gC3c>81iFv){v(Dr&>MYYuI{y;~AnN?=T5Y+z(y z)M3O4v>_PM9Md|}=F%1f+N-P$&b^KbQu=S&of zm0A>3>RDyqsqDRE9M5YgC{=A#t>q!Drp%|uXIieYo++-8QJRs{DBo!4-_`U17&D8M zdeU5w8IjqDeumDTwzyFgSKP;yZICU}CS>vYe#EBuj3IkPc)DV|Vsxjf*2uu5wplH{ z3Qu7-Z_O@HjHqkRs`#X@Um`d3T=<=6$AfIICkNc;#3J<~)9EJYOnFv$v~Df)Fza*b zpV4!uTB1-DQ8T0*J{&Ll74xUk^?Nyc&-Z4ZqoCiQAGmed zx$pjT4U=;muO)Akspq}S0iE%r8H44+9@XLK1@~5m)`=0Z(c_vUZYpJ}IQhu-#QcN> zM*KOGN4WC4ad*qB5oc!V$H}Jb=;^w*_X1YuB$eK~0*kICrL=(#YzGn+Q+*nPEgyYt8ofkk+@Sv9!- z2>srSShJ%n4H+sk8h>-g!??M2`dh#V=3k``$g6=-CZktbkot4Z3K<`({)&F_tk^@Pe21O69il zgZWE2OM#3rY4i0-LquV6+fHBSL+V-i}xyzlV?gFH=OZZI5a-u zo7}DSoqK$eeuX6R?BN=3gU^Y{C$UA}$>ToKLMju99&e#z>tl;@iZ z9$DgXhy=|e*y0)zn;bjkgF&o zBaL>!SOTX2s|gtYB{_xYznJ)M#&!YtR56w|XlW-;zz|B1zy%2RPe^tnkT(qAQ?{{m zA;9w~q7$7yUQSx3vm1^1{D-096SCQ;}?Vx*6p7$C=~whn4ln`g8xe=B>3-q$p1qp zEI>H^{BtZn67jEm{DQ*2&Dqr%ZDWsd{`ucQ%f=f+_+Efd)5(cY^3$>s{y!)>Vx0)} z|FexE>;O?L0*>X!AQ0wgK> ChatLocationInput { + switch location { + case let .peer(peerId): + return .peer(peerId) + case let .replyThread(messageId): + return .external(messageId.peerId, self.peerChannelMemberCategoriesContextsManager.replyThread(account: self.account, messageId: messageId)) + } + } } func getAppConfiguration(transaction: Transaction) -> AppConfiguration { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ca5c2dd380..4516b42c49 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) @@ -350,9 +361,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 } @@ -1072,8 +1083,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 } } @@ -1130,11 +1141,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 @@ -1414,12 +1423,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 { @@ -1595,6 +1600,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 { @@ -2138,6 +2146,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] resultIndex in + statusController?.dismiss() + + guard let strongSelf = self else { + return + } + + if let resultIndex = resultIndex { + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(resultIndex.id), keepStack: .always)) + } + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -2197,7 +2239,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 @@ -2268,290 +2310,306 @@ 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 !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) + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation)) + |> map { view, _, _ in + return !view.entries.isEmpty + } + } + } + } + + let isReplyThread: Bool + switch chatLocation { + case .peer: + isReplyThread = false + case .replyThread: + isReplyThread = true + } + + 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, isReplyThread: isReplyThread) + 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() @@ -2963,6 +3021,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 { @@ -3061,21 +3124,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 { @@ -3105,6 +3165,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] @@ -3305,7 +3369,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 { @@ -3371,7 +3436,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 @@ -3885,15 +3950,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 @@ -3949,7 +4013,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 @@ -4724,150 +4789,158 @@ 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] messageId in + guard let strongSelf = self else { + return + } + + if let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(messageId), 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 { + 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 @@ -6183,10 +6256,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) @@ -7094,7 +7163,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 @@ -7160,7 +7229,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 @@ -7186,8 +7276,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 { @@ -7211,23 +7305,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]) { @@ -7305,9 +7397,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 @@ -7387,9 +7477,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() { @@ -7612,19 +7700,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), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: nil, tags: 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), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - /*case .group: - derivedSearchState = nil*/ - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: peer.id, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) } } @@ -7695,10 +7775,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() @@ -7832,7 +7910,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 { @@ -7924,7 +8002,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 { @@ -8112,60 +8190,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 { @@ -8788,81 +8864,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 { @@ -8894,7 +8895,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 42c0552def..b331ce5614 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -121,6 +121,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 @@ -138,7 +139,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 @@ -203,6 +275,7 @@ public final class ChatControllerInteraction { self.animateDiceSuccess = animateDiceSuccess self.greetingStickerNode = greetingStickerNode self.openPeerContextMenu = openPeerContextMenu + self.openMessageReplies = openMessageReplies self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures @@ -251,6 +324,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..7da22da5d5 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2137,10 +2137,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 +2691,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 +2744,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 a896a83966..502aec5017 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -4,6 +4,7 @@ import TelegramCore import SyncCore import TemporaryCachedPeerDataManager import Emoji +import AccountContext 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 { diff --git a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift index f66ed95d0d..b33f2c8ba0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift @@ -261,7 +261,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { 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]) + return chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), context: context, chatLocation: .peer(peerId), scheduled: false, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) } let previousView = Atomic(value: nil) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 33aca30807..2310ec327e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -579,13 +579,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !scheduled { 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, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) |> beforeNext { viewUpdate in switch viewUpdate { case let .HistoryView(view, _, _, _, _, _, _): @@ -824,7 +827,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if apply { switch chatLocation { - case .peer: + case .peer, .replyThread: if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { let _ = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: messageIndex).start() } diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 75ed081bcb..c15183ee11 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -7,8 +7,8 @@ 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 { + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) |> castError(Bool.self) |> mapToSignal { update -> Signal in switch update { @@ -26,7 +26,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, 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 +35,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), additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) @@ -65,9 +66,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), 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), count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -140,9 +141,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), 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), count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -190,7 +191,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), 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 +207,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), 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 +229,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 +254,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 +272,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/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 2466374495..e5c9f7460e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -143,7 +143,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS var canReply = false switch chatPresentationInterfaceState.chatLocation { - case .peer: + case .peer, .replyThread: if let channel = peer as? TelegramChannel { if case .member = channel.participationStatus { canReply = channel.hasPermission(.sendMessages) @@ -155,8 +155,6 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } else { canReply = true } - /*case .group: - break*/ } return canReply } @@ -302,7 +300,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) @@ -569,6 +567,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(replyThreadId) + } 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] resultIndex in + statusController?.dismiss() + + if let resultIndex = resultIndex { + interfaceInteraction.viewReplies(resultIndex.id) + } + }) + + 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) @@ -605,7 +657,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) @@ -744,94 +796,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) @@ -841,7 +805,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/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 597862487c..a03dc3a7a0 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -64,6 +64,9 @@ 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 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 4e02d0e549..f577fef571 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 { @@ -565,7 +578,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -632,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 3311a17d9a..a7447accc9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -321,7 +321,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 03517922e4..502a5a4c3b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -42,7 +42,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 37d61ca176..c4edc64623 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 } @@ -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) } @@ -1213,7 +1233,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1394,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 } @@ -1418,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 @@ -1469,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 { @@ -1535,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 @@ -1760,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)?, @@ -1967,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..621e883430 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -0,0 +1,260 @@ +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 = 28.0 + let imageSpacing: CGFloat = 26.0 + + var textLeftInset: CGFloat = 0.0 + if replyPeers.isEmpty { + textLeftInset = 32.0 + } else { + textLeftInset = 8.0 + imageSize * min(3.0, CGFloat(replyPeers.count)) + } + + 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 + 4.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: 7.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) + + 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.bounds.contains(point) { + return .ignore + } + return .none + } +} + + + + diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index 936c2e034f..6bfef25e69 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -153,7 +153,9 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 14cd68d6ef..696b054a3c 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 234d060fed..784a7af594 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -300,7 +300,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 32a60cdaad..25212c7819 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -257,7 +257,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index ad139684c6..34d074d3f1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -313,29 +313,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 f82c61b85a..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) @@ -183,7 +183,9 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 15ce63a598..3fbace1ef7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -184,7 +184,9 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index a5f4b74f8e..1d44d6006b 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1031,7 +1031,9 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1512,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) strongSelf.avatarsNode.isHidden = isBotChat let alphaTransition: ContainedViewLayoutTransition if animation.isAnimated { @@ -1790,23 +1792,29 @@ private extension PeerAvatarReference { private final class MergedAvatarsNodeArguments: NSObject { let peers: [PeerAvatarReference] let images: [PeerId: UIImage] + let imageSize: CGFloat + let imageSpacing: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage]) { + init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat) { self.peers = peers self.images = images + self.imageSize = imageSize + self.imageSpacing = imageSpacing } } -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 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 var pressed: (() -> Void)? @@ -1835,10 +1843,12 @@ 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) { + self.imageSize = imageSize + self.imageSpacing = imageSpacing 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 @@ -1873,7 +1883,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 { @@ -1897,7 +1907,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) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -1917,6 +1927,9 @@ private final class MergedAvatarsNode: ASDisplayNode { 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)) diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index 626de1c9df..4abd2baa5a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -62,7 +62,9 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { } 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 { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index c50b3ad5ad..f626844bac 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 { @@ -344,7 +357,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -396,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 15b83e1a01..e9d1981bb0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -116,7 +116,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count } else if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -136,21 +138,31 @@ 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? diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 40598db1b3..eb65f03341 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) -> 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) -> 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..4298f1bd47 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -115,6 +115,15 @@ 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 + var messageUpdated = false if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion { @@ -129,7 +138,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 +157,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 +213,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..fad3ca1ba1 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/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..4b4d5a8bb9 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -18,7 +18,7 @@ import PhoneNumberFormat import ChatTitleActivityNode enum ChatTitleContent { - case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool) + case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool, isReplyThread: Bool) case group([Peer]) case custom(String) } @@ -100,8 +100,12 @@ 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, isReplyThread): + if isReplyThread { + //TODO:localize + string = NSAttributedString(string: "Replies", 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 +182,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, isReplyThread): if let peer = peerViewMainPeer(peerView) { - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || isReplyThread { inputActivitiesAllowed = false } } @@ -266,10 +270,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, isReplyThread): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || isReplyThread { 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..e23ff0e6ac 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 dbdaa4786e..bd127291b8 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift @@ -131,6 +131,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)) diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 414c34cc5c..de072426e8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -267,7 +267,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 b8f58c38a2..8d31d19ad3 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -427,7 +427,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -1953,6 +1953,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/PeerMediaCollectionController.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift index 4fdb8667ec..b7d5fd1acf 100644 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift @@ -436,6 +436,7 @@ public class PeerMediaCollectionController: TelegramBaseController { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, @@ -553,7 +554,7 @@ public class PeerMediaCollectionController: TelegramBaseController { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _ in }, statuses: nil) self.updateInterfaceState(animated: false, { return $0 }) 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 5e6fdc18cf..3692f55492 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 { @@ -1194,6 +1194,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift index b5e386a3a8..506e7f90cb 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift @@ -55,6 +55,7 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl { fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:] fileprivate var profileDataPreloadContexts: [PeerId: ProfileDataPreloadContext] = [:] fileprivate var profileDataPhotoPreloadContexts: [PeerId: ProfileDataPhotoPreloadContext] = [:] + fileprivate var replyThreadHistoryContexts: [MessageId: ReplyThreadHistoryContext] = [:] func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { if let current = self.contexts[peerId] { @@ -261,6 +262,17 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl { } } } + + func replyThread(account: Account, messageId: MessageId) -> Signal { + let context: ReplyThreadHistoryContext + if let current = self.replyThreadHistoryContexts[messageId] { + context = current + } else { + context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId) + self.replyThreadHistoryContexts[messageId] = context + } + return context.state + } } public final class PeerChannelMemberCategoriesContextsManager { @@ -565,4 +577,21 @@ public final class PeerChannelMemberCategoriesContextsManager { } |> runOn(Queue.mainQueue()) } + + public func replyThread(account: Account, messageId: MessageId) -> Signal { + return Signal { [weak self] subscriber in + guard let strongSelf = self else { + subscriber.putCompletion() + return EmptyDisposable + } + let disposable = MetaDisposable() + strongSelf.impl.with { impl in + disposable.set(impl.replyThread(account: account, messageId: messageId).start(next: { state in + subscriber.putNext(state) + })) + } + return disposable + } + |> runOn(Queue.mainQueue()) + } } From 1f6311e414a0a7b0a56044392dcc82240b9ff212 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 1 Sep 2020 22:20:55 +0100 Subject: [PATCH 4/5] Updated comments --- .../Sources/AccountContext.swift | 8 +- .../Postbox/Sources/MessageHistoryTable.swift | 46 +- .../Sources/MessageHistoryThreadsTable.swift | 98 ++ .../Postbox/Sources/MessageHistoryView.swift | 143 ++- submodules/Postbox/Sources/Postbox.swift | 5 +- submodules/Postbox/Sources/ViewTracker.swift | 13 - .../Sources/ReplyThreadMessageAttribute.swift | 10 +- submodules/TelegramApi/Sources/Api0.swift | 11 +- submodules/TelegramApi/Sources/Api1.swift | 234 ++++- submodules/TelegramApi/Sources/Api3.swift | 68 +- .../Sources/AccountViewTracker.swift | 31 +- .../Sources/ChatHistoryPreloadManager.swift | 3 +- submodules/TelegramCore/Sources/Holes.swift | 166 ++- .../Sources/ManagedMessageHistoryHoles.swift | 2 +- .../Sources/ReplyThreadHistory.swift | 187 ++-- .../Sources/StoreMessage_Telegram.swift | 24 +- .../Sources/UpdateMessageMedia.swift | 6 +- .../Sources/UpdateMessageService.swift | 4 +- .../TelegramUI/Sources/AccountContext.swift | 36 +- .../ChatChannelSubscriberInputPanelNode.swift | 6 +- .../TelegramUI/Sources/ChatController.swift | 65 +- .../Sources/ChatControllerNode.swift | 36 +- .../Sources/ChatHistoryGridNode.swift | 513 --------- .../Sources/ChatHistoryListNode.swift | 29 +- .../Sources/ChatHistoryViewForLocation.swift | 19 +- submodules/TelegramUI/Sources/ChatInfo.swift | 3 - .../ChatInterfaceStateContextMenus.swift | 14 +- .../ChatInterfaceStateInputPanels.swift | 31 +- .../ChatInterfaceStateNavigationButtons.swift | 5 + .../ChatMessageAnimatedStickerItemNode.swift | 4 +- .../Sources/ChatMessageBubbleItemNode.swift | 4 +- .../ChatMessageCommentFooterContentNode.swift | 21 +- .../ChatMessageInstantVideoItemNode.swift | 4 +- .../TelegramUI/Sources/ChatMessageItem.swift | 2 +- .../ChatMessagePollBubbleContentNode.swift | 21 +- .../Sources/ChatMessageStickerItemNode.swift | 4 +- .../ChatPanelInterfaceInteraction.swift | 4 +- .../ChatPinnedMessageTitlePanelNode.swift | 1 + .../Sources/ChatRecentActionsController.swift | 2 +- ...ChatSendMessageActionSheetController.swift | 4 +- .../TelegramUI/Sources/ChatTitleView.swift | 28 +- .../Sources/NavigateToChatController.swift | 2 +- .../Sources/OverlayPlayerControllerNode.swift | 7 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 3 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- .../PeerMediaCollectionController.swift | 972 ------------------ .../PeerMediaCollectionControllerNode.swift | 546 ---------- .../Sources/SharedAccountContext.swift | 2 +- ...annelMemberCategoriesContextsManager.swift | 29 - 49 files changed, 1013 insertions(+), 2465 deletions(-) create mode 100644 submodules/Postbox/Sources/MessageHistoryThreadsTable.swift delete mode 100644 submodules/TelegramUI/Sources/ChatHistoryGridNode.swift delete mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionController.swift delete mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a77f51cb79..db37267557 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -252,7 +252,7 @@ public enum ChatSearchDomain: Equatable { public enum ChatLocation: Equatable { case peer(PeerId) - case replyThread(MessageId) + case replyThread(threadMessageId: MessageId, maxReadMessageId: MessageId?) } public final class NavigateToChatControllerParams { @@ -638,6 +638,9 @@ public final class TonContext { #endif +public protocol ChatLocationContextHolder: class { +} + public protocol AccountContext: class { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -666,5 +669,6 @@ public protocol AccountContext: class { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? - func chatLocationInput(for location: ChatLocation) -> ChatLocationInput + func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput + func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) } diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index d5fe03d6c8..c4f00ef448 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -72,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 @@ -80,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 @@ -90,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 @@ -269,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 { @@ -1244,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) } @@ -1426,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 { @@ -2708,7 +2724,31 @@ final class MessageHistoryTable: Table { precondition(fromIndex.id.namespace == toIndex.id.namespace) var result: [IntermediateMessage] = [] if let threadId = threadId { - var startKey: ValueBoxKey + 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 @@ -2745,7 +2785,7 @@ final class MessageHistoryTable: Table { break } startKey = lastKey - } + }*/ } else if let tag = tag { let indices: [MessageIndex] if fromIndex < toIndex { 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 170592f0f2..34d6af6ceb 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -246,15 +246,18 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { 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 } @@ -952,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/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index fad732e7de..3394d6b395 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1228,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 @@ -1310,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) @@ -1321,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) @@ -1363,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) diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 2b866c7ab9..0abae1dd7d 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -485,19 +485,6 @@ final class ViewTracker { subscriber.putNext(MessageHistoryHolesView(self.messageHistoryHolesView)) } } - - var firstExternalHolesAndTags = Set() - for (view, _) in self.messageHistoryViews.copyItems() { - /*if let (hole, direction, count) = view.firstExternalHole() { - firstExternalHolesAndTags.insert(MessageHistoryHolesViewEntry(hole: hole, direction: direction, count: count)) - }*/ - } - - if self.messageHistoryHolesView.update(firstHolesAndTags) { - for subscriber in self.messageHistoryHolesViewSubscribers.copyItems() { - subscriber.putNext(MessageHistoryHolesView(self.messageHistoryHolesView)) - } - } } private func unsentViewUpdated() { diff --git a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift index 67ece5888b..7ef8c3f65c 100644 --- a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift @@ -4,23 +4,31 @@ 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]) { + 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 ec775fdf84..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) } @@ -388,7 +389,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } - dict[-119526594] = { return Api.MessageViews.parse_messageViews($0) } + dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) } dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($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,6 +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[-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) } @@ -603,7 +606,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1820043071] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } dict[-1642487306] = { return Api.Message.parse_messageService($0) } - dict[-739735064] = { return Api.Message.parse_message($0) } + dict[-1971453315] = { return Api.Message.parse_message($0) } dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($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: @@ -1236,6 +1241,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.CdnPublicKey: _1.serialize(buffer, boxed) + case let _1 as Api.MessageReplies: + _1.serialize(buffer, boxed) case let _1 as Api.InputGame: _1.serialize(buffer, boxed) case let _1 as Api.help.CountryCode: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 4ae306faca..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) @@ -6089,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 { @@ -6789,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 } } @@ -6960,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)]) } } @@ -8355,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 { @@ -11802,32 +11892,26 @@ public extension Api { } public enum MessageViews: TypeConstructorDescription { - case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Int32?, repliesPts: Int32?, recentRepliers: [Int32]?) + case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageViews(let flags, let views, let forwards, let replies, let repliesPts, let recentRepliers): + case .messageViews(let flags, let views, let forwards, let replies): if boxed { - buffer.appendInt32(-119526594) + buffer.appendInt32(1163625789) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(replies!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(repliesPts!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentRepliers!.count)) - for item in recentRepliers! { - serializeInt32(item, buffer: buffer, boxed: false) - }} + if Int(flags) & Int(1 << 2) != 0 {replies!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageViews(let flags, let views, let forwards, let replies, let repliesPts, let recentRepliers): - return ("messageViews", [("flags", flags), ("views", views), ("forwards", forwards), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers)]) + case .messageViews(let flags, let views, let forwards, let replies): + return ("messageViews", [("flags", flags), ("views", views), ("forwards", forwards), ("replies", replies)]) } } @@ -11838,22 +11922,16 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: Int32? if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: [Int32]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + var _4: Api.MessageReplies? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4, repliesPts: _5, recentRepliers: _6) + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) } else { return nil @@ -15062,6 +15140,62 @@ public extension Api { } } + } + public enum MessageReplies: TypeConstructorDescription { + 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): + if boxed { + buffer.appendInt32(-2099001323) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(replies, buffer: buffer, boxed: false) + serializeInt32(repliesPts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRepliers!.count)) + for item in recentRepliers! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, 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): + return ("messageReplies", [("flags", flags), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers), ("channelId", channelId)]) + } + } + + public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = 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 + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5) + } + else { + return nil + } + } + } public enum InputGame: TypeConstructorDescription { case inputGameID(id: Int64, accessHash: Int64) @@ -17234,7 +17368,7 @@ public extension Api { public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) - case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, replyToTopId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Int32?, recentRepliers: [Int32]?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) + case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, replyToTopId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17256,9 +17390,9 @@ public extension Api { serializeInt32(date, buffer: buffer, boxed: false) action.serialize(buffer, true) break - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let recentRepliers, let editDate, let postAuthor, let groupedId, let restrictionReason): + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): if boxed { - buffer.appendInt32(-739735064) + buffer.appendInt32(-1971453315) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -17279,12 +17413,7 @@ public extension Api { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {serializeInt32(replies!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 25) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentRepliers!.count)) - for item in recentRepliers! { - serializeInt32(item, buffer: buffer, boxed: false) - }} + if Int(flags) & Int(1 << 23) != 0 {replies!.serialize(buffer, true)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} @@ -17303,8 +17432,8 @@ public extension Api { return ("messageEmpty", [("id", id)]) case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let recentRepliers, let editDate, let postAuthor, let groupedId, let restrictionReason): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("replyToTopId", replyToTopId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("recentRepliers", recentRepliers), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let replyToTopId, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("replyToTopId", replyToTopId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) } } @@ -17393,21 +17522,19 @@ public extension Api { if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } var _15: Int32? if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } - var _16: Int32? - if Int(_1!) & Int(1 << 23) != 0 {_16 = reader.readInt32() } - var _17: [Int32]? - if Int(_1!) & Int(1 << 25) != 0 {if let _ = reader.readInt32() { - _17 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + var _16: Api.MessageReplies? + if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.MessageReplies } } - var _18: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } - var _19: String? - if Int(_1!) & Int(1 << 16) != 0 {_19 = parseString(reader) } - var _20: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_20 = reader.readInt64() } - var _21: [Api.RestrictionReason]? + var _17: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } + var _18: String? + if Int(_1!) & Int(1 << 16) != 0 {_18 = parseString(reader) } + var _19: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_19 = reader.readInt64() } + var _20: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _21 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _20 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } let _c1 = _1 != nil let _c2 = _2 != nil @@ -17425,13 +17552,12 @@ public extension Api { let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 23) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 25) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 22) == 0) || _21 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, replyToTopId: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, recentRepliers: _17, editDate: _18, postAuthor: _19, groupedId: _20, restrictionReason: _21) + let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 22) == 0) || _20 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, replyToTopId: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, editDate: _17, postAuthor: _18, groupedId: _19, restrictionReason: _20) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 6758a82215..d7405ed468 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -3682,27 +3682,6 @@ public extension Api { }) } - 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) - filter.serialize(buffer, true) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - 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() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - 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(-39505956) @@ -3724,16 +3703,32 @@ public extension Api { }) } - public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(8956627) + 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.Messages? in + 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.Messages? + var result: Api.messages.DiscussionMessage? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages + 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 }) @@ -3758,6 +3753,27 @@ public extension Api { 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) + filter.serialize(buffer, true) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + 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() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index a2cd1a961a..806788f325 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -608,16 +608,27 @@ public final class AccountViewTracker { return account.postbox.transaction { transaction -> Void in for i in 0 ..< messageIds.count { if i < viewCounts.count { - if case let .messageViews(_, views, forwards, replies, _, recentRepliers) = viewCounts[i] { + 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 - let recentRepliersPeerIds: [PeerId]? - if let recentRepliers = recentRepliers { - recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } - } else { - recentRepliersPeerIds = nil + 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) + } + repliesCount = repliesCountValue + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } + } else { + recentRepliersPeerIds = nil + } + } } loop: for j in 0 ..< attributes.count { if let attribute = attributes[j] as? ViewCountMessageAttribute { @@ -630,13 +641,13 @@ public final class AccountViewTracker { } } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { foundReplies = true - if let replies = replies { - attributes[j] = ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? []) + if let repliesCount = repliesCount { + attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId) } } } - if !foundReplies, let replies = replies { - attributes.append(ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? [])) + 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)) }) 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/Holes.swift b/submodules/TelegramCore/Sources/Holes.swift index 53c07bf180..6df9f0d75c 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/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift index aac02349c3..e3d2082713 100644 --- a/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift @@ -55,7 +55,7 @@ func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox for (entry, disposable) in added { switch entry.hole { case let .peer(hole): - disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, count: entry.count).start()) + disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: nil, count: entry.count).start()) } } }) diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift index 57eff8064e..25507264eb 100644 --- a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -9,82 +9,111 @@ private class ReplyThreadHistoryContextImpl { private let account: Account private let messageId: MessageId - private var currentHole: (MessageHistoryExternalHolesViewEntry, Disposable)? - - struct NamespaceState: Equatable { - var sortedMessageIds: [MessageId] - var holeIndices: IndexSet - } + private var currentHole: (MessageHistoryHolesViewEntry, Disposable)? struct State: Equatable { - let messageId: MessageId - let namespaces: [MessageId.Namespace] - let namespaceStates: [MessageId.Namespace: NamespaceState] + var messageId: MessageId + var holeIndices: [MessageId.Namespace: IndexSet] + var maxReadMessageId: MessageId? } let state = Promise() private var stateValue: State { didSet { - self.state.set(.single(self.stateValue)) + if self.stateValue != oldValue { + self.state.set(.single(self.stateValue)) + } } } - init(queue: Queue, account: Account, messageId: MessageId) { + 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, namespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], namespaceStates: [:]) + self.stateValue = State(messageId: self.messageId, holeIndices: [Namespaces.Message.Cloud: IndexSet(integersIn: 1 ..< Int(Int32.max))], maxReadMessageId: maxReadMessageId) self.state.set(.single(self.stateValue)) - /*self.setCurrentHole(hole: MessageHistoryExternalHolesViewEntry( - hole: .peer(MessageHistoryViewPeerHole(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, threadId: makeMessageThreadId(self.messageId))), - direction: .range(start: MessageId(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, id: Int32.max - 1), end: MessageId(peerId: self.messageId.peerId, namespace: Namespaces.Message.Cloud, id: 1)), - count: 100 - ))*/ + 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) + }) } - func setCurrentHole(hole: MessageHistoryExternalHolesViewEntry?) { - if self.currentHole?.0 != hole { + deinit { + self.holesDisposable?.dispose() + self.readDisposable.dispose() + } + + func setCurrentHole(entry: MessageHistoryHolesViewEntry?) { + if self.currentHole?.0 != entry { self.currentHole?.1.dispose() - if let hole = hole { - self.currentHole = (hole, self.fetchHole(hole: hole).start()) + 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(hole: MessageHistoryExternalHolesViewEntry) -> Signal { - let messageId = self.messageId + 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 - return self.account.postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + 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.getReplies(peer: inputPeer, msgId: messageId.id, offsetId: Int32.max - 1, addOffset: 0, limit: Int32(hole.count), maxId: Int32.max - 1, minId: 1, hash: 0)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal in - guard let result = result else { - return .complete() - } - return account.postbox.transaction { transaction -> Void in - switch result { - case .messages(let messages, let chats, let users), .messagesSlice(_, _, _, let messages, let chats, let users), .channelMessages(_, _, _, let messages, let chats, let users): - break - case .messagesNotModified: - break - } - } - |> ignoreValues + 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()) } } @@ -111,7 +140,10 @@ public class ReplyThreadHistoryContext { 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), holes: [:] + peerId: state.messageId.peerId, + threadId: makeMessageThreadId(state.messageId), + maxReadMessageId: state.maxReadMessageId, + holes: state.holeIndices )) }) disposable.set(stateDisposable) @@ -121,43 +153,82 @@ public class ReplyThreadHistoryContext { } } - public init(account: Account, peerId: PeerId, threadMessageId: MessageId) { + 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) + 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 func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal { +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 + |> 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 + |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in guard let result = result else { return .single(nil) } - return account.postbox.transaction { transaction -> MessageIndex? in + return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in switch result { - case .messages(let messages, let chats, let users), .messagesSlice(_, _, _, let messages, let chats, let users), .channelMessages(_, _, _, let messages, let chats, let users): - guard let message = messages.first else { + case let .discussionMessage(message, readMaxId, chats, users): + guard let parsedMessage = StoreMessage(apiMessage: message), let parsedIndex = parsedMessage.index else { return nil } - guard let parsedMessage = StoreMessage(apiMessage: message) else { - return nil + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } } - return parsedMessage.index - case .messagesNotModified: - return nil + 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/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 929d0af677..726ed01499 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -137,7 +137,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, _, media, _, entities, _, _, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, _, media, _, entities, _, _, _, _, _, _, _): let peerId: PeerId switch toId { case let .peerUser(userId): @@ -241,7 +241,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { switch message { - case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyToMsgId = replyToMsgId { let peerId: PeerId switch toId { @@ -399,7 +399,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, replyToTopId, date, message, media, replyMarkup, entities, views, forwards, replies, recentRepliers, editDate, postAuthor, groupingId, restrictionReason): + case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, replyToTopId, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason): let peerId: PeerId var authorId: PeerId? switch toId { @@ -577,13 +577,19 @@ extension StoreMessage { if let replies = replies { let recentRepliersPeerIds: [PeerId]? - if let recentRepliers = recentRepliers { - recentRepliersPeerIds = recentRepliers.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: $0) } - } else { - recentRepliersPeerIds = nil + //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)) } - - attributes.append(ReplyThreadMessageAttribute(count: replies, latestUsers: recentRepliersPeerIds ?? [])) } if let restrictionReason = restrictionReason { diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 5b9088c408..3c304d59e1 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -78,15 +78,15 @@ private func updateMessageThreadStatsInternal(transaction: Transaction, threadMe 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)) + 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 { + if !updated && isGroup { let count = max(0, countDifference) - attributes.append(ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: [], added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0))) + 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)) }) diff --git a/submodules/TelegramCore/Sources/UpdateMessageService.swift b/submodules/TelegramCore/Sources/UpdateMessageService.swift index fcb62ef9ee..099e574aeb 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, recentRepliers: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -75,7 +75,7 @@ class UpdateMessageService: NSObject, MTMessageService { generatedToId = Api.Peer.peerUser(userId: self.peerId.id) } - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, recentRepliers: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, replyToTopId: nil, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index a4830ca8ec..919fac2fdc 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -299,14 +299,44 @@ public final class AccountContextImpl: AccountContext { } } - public func chatLocationInput(for location: ChatLocation) -> ChatLocationInput { + public func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput { switch location { case let .peer(peerId): return .peer(peerId) - case let .replyThread(messageId): - return .external(messageId.peerId, self.peerChannelMemberCategoriesContextsManager.replyThread(account: self.account, messageId: messageId)) + 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 f935012e2d..8ae1d594ac 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -66,7 +66,7 @@ extension ChatLocation { switch self { case let .peer(peerId): return peerId - case let .replyThread(messageId): + case let .replyThread(messageId, _): return messageId.peerId } } @@ -337,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 @@ -1610,7 +1612,7 @@ 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): + 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) } @@ -2161,7 +2163,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let foundIndex = Promise() + let foundIndex = Promise() foundIndex.set(fetchChannelReplyThreadMessage(account: strongSelf.context.account, messageId: messageId)) var cancelImpl: (() -> Void)? @@ -2172,16 +2174,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let disposable = (foundIndex.get() |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] resultIndex in + |> deliverOnMainQueue).start(next: { [weak statusController] result in statusController?.dismiss() guard let strongSelf = self else { return } - if let resultIndex = resultIndex { + if let result = result { if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(resultIndex.id), keepStack: .always)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: result.messageId, maxReadMessageId: result.maxReadMessageId), keepStack: .always)) } } }) @@ -2324,7 +2326,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch chatLocation { case let .peer(peerId): chatLocationPeerId = peerId - case let .replyThread(messageId): + case let .replyThread(messageId, _): chatLocationPeerId = messageId.peerId } @@ -2377,14 +2379,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.reportIrrelvantGeoNoticePromise.set(.single(nil)) } - if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat { + 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)) + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) |> map { view, _, _ in return !view.entries.isEmpty } @@ -2393,18 +2396,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let isReplyThread: Bool + let replyThreadType: ChatTitleContent.ReplyThreadType? switch chatLocation { - case .peer: - isReplyThread = false - case .replyThread: + 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, isReplyThread: isReplyThread) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, repleThread: replyThreadType) let imageOverride: AvatarNodeImageOverride? if strongSelf.context.account.peerId == peer.id { imageOverride = .savedMessagesIcon @@ -2984,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 { @@ -3032,7 +3043,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { } - if case let .replyThread(messageId) = strongSelf.chatLocation { + if case let .replyThread(messageId, _) = strongSelf.chatLocation { pinnedMessageId = messageId } @@ -3175,7 +3186,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = cachedData as? CachedSecretChatData { } - if case let .replyThread(messageId) = strongSelf.chatLocation { + if case let .replyThread(messageId, _) = strongSelf.chatLocation { pinnedMessageId = messageId } @@ -4799,19 +4810,21 @@ 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] messageId in + }, viewReplies: { [weak self] sourceMessageId, replyThreadResult in guard let strongSelf = self else { return } if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(messageId), keepStack: .always)) + 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())) 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])) @@ -5412,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 @@ -6468,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 @@ -6681,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) }) } @@ -7243,7 +7250,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch self.chatLocation { case .peer: break - case let .replyThread(messageId): + case let .replyThread(messageId, _): defaultReplyMessageId = messageId } @@ -7290,7 +7297,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch self.chatLocation { case let .peer(peerIdValue): peerId = peerIdValue - case let .replyThread(messageId): + case let .replyThread(messageId, _): peerId = messageId.peerId } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 7da22da5d5..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 diff --git a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift deleted file mode 100644 index b33f2c8ba0..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), context: context, 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 2310ec327e..bd3edfb56e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -507,7 +507,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 @@ -562,6 +562,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 { @@ -576,10 +580,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { additionalData.append(.peerIsContact(peerId)) } } - if !scheduled { + if !isAuxiliaryChat { additionalData.append(.totalUnreadState) } - if case let .replyThread(messageId) = chatLocation { + if case let .replyThread(messageId, _) = chatLocation { additionalData.append(.message(messageId)) } @@ -588,7 +592,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let historyViewUpdate = self.chatHistoryLocationPromise.get() |> distinctUntilChanged |> mapToSignal { location in - return chatHistoryViewForLocation(location, context: context, 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, _, _, _, _, _, _): @@ -706,7 +710,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 { @@ -827,9 +834,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if apply { switch chatLocation { - case .peer, .replyThread: - 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) } } } @@ -1446,12 +1453,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 c15183ee11..bad6e9fce0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -8,7 +8,8 @@ import Display import AccountContext func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { - return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) + 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,7 @@ func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, c |> restartIfError } -func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, 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 @@ -35,7 +36,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up chatScrollPosition = .index(index: index, position: position, directionHint: directionHint, animated: animated) } - return account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: 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) @@ -66,9 +67,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> if let tagMask = tagMask { - signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: 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(context.chatLocationInput(for: 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 @@ -141,9 +142,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> switch searchLocation { case let .index(index): - signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: 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(context.chatLocationInput(for: 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 @@ -191,7 +192,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A } case let .Navigation(index, anchorIndex, count): var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: 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 @@ -207,7 +208,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: 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(context.chatLocationInput(for: 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) 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/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 90118a4ee6..b08c266d6e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -143,7 +143,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS var canReply = false switch chatPresentationInterfaceState.chatLocation { - case .peer, .replyThread: + case .peer: if let channel = peer as? TelegramChannel { if case .member = channel.participationStatus { canReply = channel.hasPermission(.sendMessages) @@ -155,6 +155,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } else { canReply = true } + case .replyThread: + canReply = true } return canReply } @@ -591,14 +593,14 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: 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() + 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(replyThreadId) + interfaceInteraction.viewReplies(messages[0].id, ChatReplyThreadMessage(messageId: replyThreadId, maxReadMessageId: nil)) } else { var cancelImpl: (() -> Void)? let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { @@ -608,11 +610,11 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: let disposable = (foundIndex.get() |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] resultIndex in + |> deliverOnMainQueue).start(next: { [weak statusController] result in statusController?.dismiss() - if let resultIndex = resultIndex { - interfaceInteraction.viewReplies(resultIndex.id) + if let result = result { + interfaceInteraction.viewReplies(nil, result) } }) 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 a03dc3a7a0..233339ca6b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -67,6 +67,11 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch 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/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index f577fef571..4404f27d79 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -466,7 +466,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId): + case let .replyThread(messageId, _): if messageId.peerId != item.context.account.peerId { if messageId.peerId.isGroupOrChannel && item.message.author != nil { var isBroadcastChannel = false @@ -647,7 +647,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + 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)) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index c4edc64623..fe0a4a7627 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -809,7 +809,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode switch item.chatLocation { case let .peer(peerId): chatLocationPeerId = peerId - case let .replyThread(messageId): + case let .replyThread(messageId, _): chatLocationPeerId = messageId.peerId } @@ -1003,7 +1003,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = attribute.title } } else if let attribute = attribute as? ReplyMessageAttribute { - if case let .replyThread(replyThreadMessageId) = item.chatLocation, replyThreadMessageId == attribute.messageId { + if case let .replyThread(replyThreadMessageId, _) = item.chatLocation, replyThreadMessageId == attribute.messageId { } else { replyMessage = firstMessage.associatedMessages[attribute.messageId] } diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift index 621e883430..827e5b6ab3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -127,14 +127,14 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { rawText = "Leave a Comment" } - let imageSize: CGFloat = 28.0 - let imageSpacing: CGFloat = 26.0 + 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(3.0, CGFloat(replyPeers.count)) + 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) @@ -151,7 +151,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { 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 + 4.0), size: textLayout.size) + 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) @@ -210,7 +210,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { if let arrowImage = arrowImage { strongSelf.arrowNode.image = arrowImage - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: boundingWidth - 27.0, y: 7.0), size: arrowImage.size) + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: boundingWidth - 27.0, y: 8.0), size: arrowImage.size) } strongSelf.iconNode.isHidden = !replyPeers.isEmpty @@ -218,7 +218,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { 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) + 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)) @@ -248,11 +248,18 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { } override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { - if self.bounds.contains(point) { + 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/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 696b054a3c..d9bb1312d3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -182,7 +182,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { switch item.chatLocation { case let .peer(peerId): messagePeerId = peerId - case let .replyThread(messageId): + case let .replyThread(messageId, _): messagePeerId = messageId.peerId } @@ -337,7 +337,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + 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)) } diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 34d074d3f1..a1ae1446d7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -317,7 +317,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { switch chatLocation { case let .peer(peerId): messagePeerId = peerId - case let .replyThread(messageId): + case let .replyThread(messageId, _): messagePeerId = messageId.peerId } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index 1d44d6006b..2668adcf64 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1517,7 +1517,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { 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, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing) + 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 { @@ -1794,17 +1794,20 @@ private final class MergedAvatarsNodeArguments: NSObject { let images: [PeerId: UIImage] let imageSize: CGFloat let imageSpacing: CGFloat + let borderWidth: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat) { + 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 defaultMergedImageSize: CGFloat = 16.0 private let defaultMergedImageSpacing: CGFloat = 15.0 +private let defaultBorderWidth: CGFloat = 1.0 private let avatarFont = avatarPlaceholderFont(size: 8.0) @@ -1815,6 +1818,7 @@ final class MergedAvatarsNode: ASDisplayNode { private let buttonNode: HighlightTrackingButtonNode private var imageSize: CGFloat = defaultMergedImageSize private var imageSpacing: CGFloat = defaultMergedImageSpacing + private var borderWidthValue: CGFloat = defaultBorderWidth var pressed: (() -> Void)? @@ -1843,9 +1847,10 @@ final class MergedAvatarsNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat) { + 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 { filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3) @@ -1907,7 +1912,7 @@ final class MergedAvatarsNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { - return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing) + 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) { @@ -1925,16 +1930,16 @@ 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] { @@ -1942,7 +1947,7 @@ 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/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index f626844bac..29320c50b2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -249,7 +249,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId): + case let .replyThread(messageId, _): if messageId.peerId != item.context.account.peerId { if messageId.peerId.isGroupOrChannel && item.message.author != nil { var isBroadcastChannel = false @@ -411,7 +411,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + 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)) } diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index eb65f03341..9d74eed519 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -120,7 +120,7 @@ final class ChatPanelInterfaceInteraction { let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let openPeersNearby: () -> Void let unarchivePeer: () -> Void - let viewReplies: (MessageId) -> Void + let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? init( @@ -194,7 +194,7 @@ final class ChatPanelInterfaceInteraction { openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, - viewReplies: @escaping (MessageId) -> Void, + viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 4298f1bd47..016edcb7d3 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -123,6 +123,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } self.closeButton.isHidden = isReplyThread + self.tapButton.isUserInteractionEnabled = !isReplyThread var messageUpdated = false if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index fad3ca1ba1..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: {}, viewReplies: { _ in }, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.navigationItem.titleView = self.titleView 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/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 4b4d5a8bb9..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, isReplyThread: Bool) + enum ReplyThreadType { + case replies + case comments + } + + case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool, repleThread: ReplyThreadType?) case group([Peer]) case custom(String) } @@ -100,10 +105,17 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var titleScamIcon = false var isEnabled = true switch titleContent { - case let .peer(peerView, _, isScheduledMessages, isReplyThread): - if isReplyThread { + case let .peer(peerView, _, isScheduledMessages, replyThreadType): + if let replyThreadType = replyThreadType { //TODO:localize - string = NSAttributedString(string: "Replies", font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) + 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 { @@ -182,9 +194,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, isScheduledMessages, isReplyThread): + case let .peer(peerView, _, isScheduledMessages, replyThreadType): if let peer = peerViewMainPeer(peerView) { - if peer.id == self.account.peerId || isScheduledMessages || isReplyThread { + if peer.id == self.account.peerId || isScheduledMessages || replyThreadType != nil || peer.id.id == 708513 { inputActivitiesAllowed = false } } @@ -270,10 +282,10 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, onlineMemberCount, isScheduledMessages, isReplyThread): + case let .peer(peerView, onlineMemberCount, isScheduledMessages, replyThreadType): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) - if peer.id == self.account.peerId || isScheduledMessages || isReplyThread { + 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/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index e23ff0e6ac..b09a06020e 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -125,7 +125,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if message.id.peerId == peerId { return true } - case let .replyThread(messageId): + 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 bd127291b8..b927c3022e 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift @@ -160,7 +160,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)) + 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)) super.init() @@ -490,7 +492,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)) + 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)) 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 de072426e8..173d501e93 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -70,7 +70,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)) + 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)) self.listNode.defaultToSynchronousTransactionWhileScrolling = true if tagMask == .music { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 8d31d19ad3..37a4156cd6 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -427,7 +427,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _ in }, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift deleted file mode 100644 index b7d5fd1acf..0000000000 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift +++ /dev/null @@ -1,972 +0,0 @@ -import Foundation -import UIKit -import Postbox -import SwiftSignalKit -import Display -import AsyncDisplayKit -import TelegramCore -import SyncCore -import SafariServices -import TelegramPresentationData -import TelegramUIPreferences -import TelegramBaseController -import OverlayStatusController -import AccountContext -import ShareController -import OpenInExternalAppUI -import PeerInfoUI -import ContextUI -import PresentationDataUtils -import LocalizedPeerData - -public class PeerMediaCollectionController: TelegramBaseController { - private var validLayout: ContainerViewLayout? - - private let context: AccountContext - private let peerId: PeerId - private let messageId: MessageId? - - private let peerDisposable = MetaDisposable() - private let navigationActionDisposable = MetaDisposable() - - private let messageIndexDisposable = MetaDisposable() - - private let _peerReady = Promise() - private var didSetPeerReady = false - private let peer = Promise(nil) - - private var interfaceState: PeerMediaCollectionInterfaceState - - private var rightNavigationButton: PeerMediaCollectionNavigationButton? - - private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() - private var presentationDataDisposable:Disposable? - - private var controllerInteraction: ChatControllerInteraction? - private var interfaceInteraction: ChatPanelInterfaceInteraction? - - private let messageContextDisposable = MetaDisposable() - private var shareStatusDisposable: MetaDisposable? - - private var presentationData: PresentationData - - private var resolveUrlDisposable: MetaDisposable? - - public init(context: AccountContext, peerId: PeerId, messageId: MessageId? = nil) { - self.context = context - self.peerId = peerId - self.messageId = messageId - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.interfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) - - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none) - - self.navigationPresentation = .modalInLargeLayout - - self.title = self.presentationData.strings.SharedMedia_TitleAll - - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - - self.ready.set(.never()) - - self.scrollToTop = { [weak self] in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToEndOfHistory() - } - } - - self.presentationDataDisposable = (context.sharedContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings - let previousChatWallpaper = strongSelf.presentationData.chatWallpaper - - strongSelf.presentationData = presentationData - - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { - strongSelf.themeAndStringsUpdated() - } - } - }) - - let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, mode in - if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { - guard let navigationController = strongSelf.navigationController as? NavigationController else { - return false - } - strongSelf.mediaCollectionDisplayNode.view.endEditing(true) - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: galleryMessage.message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { - self?.mediaCollectionDisplayNode.view.endEditing(true) - }, present: { c, a in - self?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { messageId, media in - if let strongSelf = self { - return strongSelf.mediaCollectionDisplayNode.transitionNodeForGallery(messageId: messageId, media: media) - } - return nil - }, addToTransitionSurface: { view in - if let strongSelf = self { - var belowSubview: UIView? - if let historyNode = strongSelf.mediaCollectionDisplayNode.historyNode as? ChatHistoryGridNode { - if let lowestSectionNode = historyNode.lowestSectionNode() { - belowSubview = lowestSectionNode.view - } - } - strongSelf.mediaCollectionDisplayNode.historyNode - if let belowSubview = belowSubview { - strongSelf.mediaCollectionDisplayNode.historyNode.view.insertSubview(view, belowSubview: belowSubview) - } else { - strongSelf.mediaCollectionDisplayNode.historyNode.view.addSubview(view) - } - } - }, openUrl: { url in - self?.openUrl(url) - }, openPeer: { peer, navigation in - self?.controllerInteraction?.openPeer(peer.id, navigation, nil) - }, callPeer: { peerId, isVideo in - self?.controllerInteraction?.callPeer(peerId, isVideo) - }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) - } - return false - }, openPeer: { [weak self] id, navigation, _ in - if let strongSelf = self, let id = id, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id))) - } - }, openPeerMention: { _ in - }, openMessageContextMenu: { [weak self] message, _, _, _, _ in - guard let strongSelf = self else { - return - } - (chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) - |> deliverOnMainQueue).start(next: { actions in - var messageIds = Set() - messageIds.insert(message.id) - - if let strongSelf = self, strongSelf.isNodeLoaded { - if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetButtonItem] = [] - - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.SharedMedia_ViewInChat, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) - } - })) - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuForward, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.forwardMessages(messageIds) - } - })) - if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { - items.append( ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.deleteMessages(messageIds) - } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.mediaCollectionDisplayNode.view.endEditing(true) - strongSelf.present(actionSheet, in: .window(.root)) - } - } - }) - }, openMessageContextActions: { [weak self] message, node, rect, gesture in - guard let strongSelf = self else { - gesture?.cancel() - return - } - - let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController) - |> deliverOnMainQueue).start(next: { previewData in - guard let strongSelf = self else { - gesture?.cancel() - return - } - if let previewData = previewData { - let context = strongSelf.context - let strings = strongSelf.presentationData.strings - let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) - |> map { actions -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) - } - }) - }))) - - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.forwardMessages([message.id]) - } - }) - }))) - - if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.setItems(context.account.postbox.transaction { transaction -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - let messageIds = [message.id] - - if let peer = transaction.getPeer(message.id.peerId) { - var personalPeerName: String? - var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = user.compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if actions.options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - } else if let personalPeerName = personalPeerName { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 - } else { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone - } - items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() - } - }) - }))) - } - - if actions.options.contains(.deleteLocally) { - var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - if strongSelf.context.account.peerId == strongSelf.peerId { - if messageIds.count == 1 { - localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages - } - } - items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() - } - }) - }))) - } - } - - return items - }) - }))) - } - - return items - } - - switch previewData { - case let .gallery(gallery): - gallery.setHintWillBePresentedInPreviewingContext(true) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) - case .instantPage: - break - } - } - }) - }, navigateToMessage: { [weak self] fromId, id in - if let strongSelf = self, strongSelf.isNodeLoaded { - if id.peerId == strongSelf.peerId { - var fromIndex: MessageIndex? - - if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(fromId) { - fromIndex = message.index - } - - /*if let fromIndex = fromIndex { - if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(id) { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: MessageIndex(message)) - } else { - strongSelf.messageIndexDisposable.set((strongSelf.account.postbox.messageIndexAtId(id) |> deliverOnMainQueue).start(next: { [weak strongSelf] index in - if let strongSelf = strongSelf, let index = index { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index) - } - })) - } - }*/ - } else { - (strongSelf.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id.peerId), subject: .message(id))) - } - } - }, tapMessage: nil, clickThroughMessage: { [weak self] in - self?.view.endEditing(true) - }, toggleMessagesSelection: { [weak self] ids, value in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) }) - } - }, sendCurrentMessage: { _ in - }, sendMessage: { _ in - }, sendSticker: { _, _, _, _ in - return false - }, sendGif: { _, _, _ in - return false - }, sendBotContextResultAsGif: { _, _, _, _ in - return false - }, requestMessageActionCallback: { _, _, _, _ in - }, requestMessageActionUrlAuth: { _, _, _ in - }, activateSwitchInline: { _, _ in - }, openUrl: { [weak self] url, _, external, _ in - self?.openUrl(url, external: external ?? false) - }, shareCurrentLocation: { - }, shareAccountContact: { - }, sendBotCommand: { _, _ in - }, openInstantPage: { [weak self] message, associatedData in - if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.navigationController as? NavigationController, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) - } - }, openWallpaper: { [weak self] message in - if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a, blockInteraction: true) - }) - } - }, openTheme: { _ in - }, openHashtag: { _, _ in - }, updateInputState: { _ in - }, updateInputMode: { _ in - }, openMessageShareMenu: { _ in - }, presentController: { _, _ in - }, navigationController: { - return nil - }, chatControllerNode: { - return nil - }, reactionContainerNode: { - return nil - }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in - }, longTap: { [weak self] content, _ in - if let strongSelf = self { - strongSelf.view.endEditing(true) - switch content { - case let .url(url): - let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: .url(url: url)).count > 1 - let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: url), - ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - if canOpenIn { - let actionSheet = OpenInActionSheetController(context: strongSelf.context, item: .url(url: url), openUrl: { [weak self] url in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { - }) - } - }) - strongSelf.present(actionSheet, in: .window(.root)) - } else { - strongSelf.context.sharedContext.applicationBindings.openUrl(url) - } - } - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = url - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let link = URL(string: url) { - let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) - } - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - default: - break - } - } - }, openCheckoutOrReceipt: { _ in - }, openSearch: { [weak self] in - self?.activateSearch() - }, setupReply: { _ in - }, canSetupReply: { _ in - return .none - }, navigateToFirstDateMessage: { _ in - }, requestRedeliveryOfFailedMessages: { _ in - }, addContact: { _ in - }, rateCall: { _, _, _ in - }, requestSelectMessagePollOptions: { _, _ in - }, requestOpenMessagePollResults: { _, _ in - }, openAppStorePage: { - }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { - }, sendScheduledMessagesNow: { _ in - }, editScheduledMessagesTime: { _ in - }, performTextSelectionAction: { _, _, _ in - }, updateMessageLike: { _, _ in - }, openMessageReactions: { _ in - }, displaySwipeToReplyHint: { - }, dismissReplyMarkupMessage: { _ in - }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in - }, displayPollSolution: { _, _ in - }, displayPsa: { _, _ in - }, displayDiceTooltip: { _ in - }, animateDiceSuccess: { - }, greetingStickerNode: { - return nil - }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in - }, requestMessageUpdate: { _ in - }, cancelInteractiveKeyboardGestures: { - }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, - pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) - - self.controllerInteraction = controllerInteraction - - self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in - }, setupEditMessage: { _, _ in - }, beginMessageSelection: { _, _ in - }, deleteSelectedMessages: { [weak self] in - if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds { - strongSelf.deleteMessages(messageIds) - } - }, reportSelectedMessages: { [weak self] in - if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { - strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in - self?.present(c, in: .window(.root), with: a) - }, push: { c in - self?.push(c) - }, completion: { _ in }), in: .window(.root)) - } - }, reportMessages: { _, _ in - }, deleteMessages: { _, _, f in - f(.default) - }, forwardSelectedMessages: { [weak self] in - if let strongSelf = self { - if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds { - strongSelf.forwardMessages(forwardMessageIdsSet) - } - } - }, forwardCurrentForwardMessages: { - }, forwardMessages: { _ in - }, shareSelectedMessages: { [weak self] in - if let strongSelf = self, let selectedIds = strongSelf.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in - var messages: [Message] = [] - for id in selectedIds { - if let message = transaction.getMessage(id) { - messages.append(message) - } - } - return messages - } |> deliverOnMainQueue).start(next: { messages in - if let strongSelf = self, !messages.isEmpty { - strongSelf.updateInterfaceState(animated: true, { - $0.withoutSelectionState() - }) - - let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in - return lhs.index < rhs.index - })), externalShare: true, immediateExternalShare: true) - strongSelf.present(shareController, in: .window(.root)) - } - }) - } - }, updateTextInputStateAndMode: { _ in - }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in - }, openStickers: { - }, editMessage: { - }, beginMessageSearch: { _, _ in - }, dismissMessageSearch: { - }, updateMessageSearch: { _ in - }, openSearchResults: { - }, navigateMessageSearch: { _ in - }, openCalendarSearch: { - }, toggleMembersSearch: { _ in - }, navigateToMessage: { _ in - }, navigateToChat: { _ in - }, navigateToProfile: { _ in - }, openPeerInfo: { - }, togglePeerNotifications: { - }, sendContextResult: { _, _, _, _ in - return false - }, sendBotCommand: { _, _ in - }, sendBotStart: { _ in - }, botSwitchChatWithPayload: { _, _ in - }, beginMediaRecording: { _ in - }, finishMediaRecording: { _ in - }, stopMediaRecording: { - }, lockMediaRecording: { - }, deleteRecordedMedia: { - }, sendRecordedMedia: { - }, displayRestrictedInfo: { _, _ in - }, displayVideoUnmuteTip: { _ in - }, switchMediaRecordingMode: { - }, setupMessageAutoremoveTimeout: { - }, sendSticker: { _, _, _ in - return false - }, unblockPeer: { - }, pinMessage: { _ in - }, unpinMessage: { - }, shareAccountContact: { - }, reportPeer: { - }, presentPeerContact: { - }, dismissReportPeer: { - }, deleteChat: { - }, beginCall: { _ in - }, toggleMessageStickerStarred: { _ in - }, presentController: { _, _ in - }, getNavigationController: { - return nil - }, presentGlobalOverlayController: { _, _ in - }, navigateFeed: { - }, openGrouping: { - }, toggleSilentPost: { - }, requestUnvoteInMessage: { _ in - }, requestStopPollInMessage: { _ in - }, updateInputLanguage: { _ in - }, unarchiveChat: { - }, openLinkEditing: { - }, reportPeerIrrelevantGeoLocation: { - }, displaySlowmodeTooltip: { _, _ in - }, displaySendMessageOptions: { _, _ in - }, openScheduledMessages: { - }, openPeersNearby: { - }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _ in }, statuses: nil) - - self.updateInterfaceState(animated: false, { return $0 }) - - self.peer.set(context.account.postbox.peerView(id: peerId) |> map { $0.peers[$0.peerId] }) - - self.peerDisposable.set((self.peer.get() - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: false, { return $0.withUpdatedPeer(peer) }) - if !strongSelf.didSetPeerReady { - strongSelf.didSetPeerReady = true - strongSelf._peerReady.set(.single(true)) - } - } - })) - } - - private func themeAndStringsUpdated() { - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) - // self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) - self.updateInterfaceState(animated: false, { state in - var state = state - state = state.updatedTheme(self.presentationData.theme) - state = state.updatedStrings(self.presentationData.strings) - return state - }) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.messageIndexDisposable.dispose() - self.navigationActionDisposable.dispose() - self.galleryHiddenMesageAndMediaDisposable.dispose() - self.messageContextDisposable.dispose() - self.resolveUrlDisposable?.dispose() - self.presentationDataDisposable?.dispose() - } - - var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode { - get { - return super.displayNode as! PeerMediaCollectionControllerNode - } - } - - override public func loadDisplayNode() { - self.displayNode = PeerMediaCollectionControllerNode(context: self.context, peerId: self.peerId, messageId: self.messageId, controllerInteraction: self.controllerInteraction!, interfaceInteraction: self.interfaceInteraction!, navigationBar: self.navigationBar, requestDeactivateSearch: { [weak self] in - self?.deactivateSearch() - }) - - let mediaManager = self.context.sharedContext.mediaManager - self.galleryHiddenMesageAndMediaDisposable.set(mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] - - for id in ids { - if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { - messageIdAndMedia[messageId] = [media] - } - } - - //if controllerInteraction.hiddenMedia != messageIdAndMedia { - controllerInteraction.hiddenMedia = messageIdAndMedia - - strongSelf.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? ListMessageNode { - itemNode.updateHiddenMedia() - } - } - //} - } - })) - - self.ready.set(combineLatest(self.mediaCollectionDisplayNode.historyNode.historyState.get(), self._peerReady.get()) |> map { $1 }) - - self.mediaCollectionDisplayNode.requestLayout = { [weak self] transition in - self?.requestLayout(transition: transition) - } - - self.mediaCollectionDisplayNode.requestUpdateMediaCollectionInterfaceState = { [weak self] animated, f in - self?.updateInterfaceState(animated: animated, f) - } - - self.displayNodeDidLoad() - } - - override public func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.mediaCollectionDisplayNode.historyNode.preloadPages = true - } - - override public func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - self.mediaCollectionDisplayNode.clearHighlightAnimated(true) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.validLayout = layout - - self.mediaCollectionDisplayNode.containerLayoutUpdated(layout, navigationBarHeightAndPrimaryHeight: (self.navigationHeight, self.primaryNavigationHeight), transition: transition, listViewTransaction: { updateSizeAndInsets in - self.mediaCollectionDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) - }) - } - - func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) { - let updatedInterfaceState = f(self.interfaceState) - - if self.isNodeLoaded { - self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated) - } - self.interfaceState = updatedInterfaceState - - if let button = rightNavigationButtonForPeerMediaCollectionInterfaceState(updatedInterfaceState, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction)) { - if self.rightNavigationButton != button { - self.navigationItem.setRightBarButton(button.buttonItem, animated: true) - } - self.rightNavigationButton = button - } else if let _ = self.rightNavigationButton { - self.navigationItem.setRightBarButton(nil, animated: true) - self.rightNavigationButton = nil - } - - if let controllerInteraction = self.controllerInteraction { - if updatedInterfaceState.selectionState != controllerInteraction.selectionState { - let animated = animated || controllerInteraction.selectionState == nil || updatedInterfaceState.selectionState == nil - controllerInteraction.selectionState = updatedInterfaceState.selectionState - self.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateSelectionState(animated: animated) - } else if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateSelectionState(animated: animated) - } - } - - self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds - view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil && self.mediaCollectionDisplayNode.historyNode is ChatHistoryGridNode - } - } - } - - @objc func rightNavigationButtonAction() { - if let button = self.rightNavigationButton { - self.navigationButtonAction(button.action) - } - } - - private func navigationButtonAction(_ action: PeerMediaCollectionNavigationButtonAction) { - switch action { - case .cancelMessageSelection: - self.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - case .beginMessageSelection: - self.updateInterfaceState(animated: true, { $0.withSelectionState() }) - } - } - - private func activateSearch() { - if self.displayNavigationBar { - if let scrollToTop = self.scrollToTop { - scrollToTop() - } - self.mediaCollectionDisplayNode.activateSearch() - self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) - } - } - - private func deactivateSearch() { - if !self.displayNavigationBar { - self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) - self.mediaCollectionDisplayNode.deactivateSearch() - } - } - - private func openUrl(_ url: String, external: Bool = false) { - let disposable: MetaDisposable - if let current = self.resolveUrlDisposable { - disposable = current - } else { - disposable = MetaDisposable() - self.resolveUrlDisposable = disposable - } - - let resolvedUrl: Signal - if external { - resolvedUrl = .single(.externalUrl(url)) - } else { - resolvedUrl = self.context.sharedContext.resolveUrl(account: self.context.account, url: url) - } - - disposable.set((resolvedUrl |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.navigationController as? NavigationController, openPeer: { peerId, navigation in - if let strongSelf = self { - switch navigation { - case let .chat(_, subject, peekData): - if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData)) - } - case .info: - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { - (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) - } - } - })) - case let .withBotStartPayload(startPayload): - if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload)) - } - default: - break - } - } - }, sendFile: nil, - sendSticker: nil, - present: { c, a in - self?.present(c, in: .window(.root), with: a) - }, dismissInput: { - self?.view.endEditing(true) - }, contentContext: nil) - } - })) - } - - func forwardMessages(_ messageIds: Set) { - let currentMessages = (self.mediaCollectionDisplayNode.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode)?.currentMessages - let _ = (self.context.account.postbox.transaction { transaction -> Void in - for id in messageIds { - if transaction.getMessage(id) == nil { - if let message = currentMessages?[id] { - storeMessageFromSearch(transaction: transaction, message: message) - } - } - } - } - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let strongSelf = self else { - return - } - let forwardMessageIds = Array(messageIds).sorted() - - let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.onlyWriteable, .excludeDisabled])) - controller.peerSelected = { [weak controller] peerId in - if let strongSelf = self, let _ = controller { - if peerId == strongSelf.context.account.peerId { - strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) - - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in - return .forward(source: id, grouping: .auto, attributes: []) - }) - |> deliverOnMainQueue).start(next: { [weak self] messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - if strongSelf.shareStatusDisposable == nil { - strongSelf.shareStatusDisposable = MetaDisposable() - } - strongSelf.shareStatusDisposable?.set((combineLatest(signals) - |> deliverOnMainQueue).start(completed: { - guard let strongSelf = self else { - return - } - strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root)) - })) - } - }) - if let strongController = controller { - strongController.dismiss() - } - } else { - let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedForwardMessageIds(forwardMessageIds) - } else { - return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds) - } - }) - }) |> deliverOnMainQueue).start(completed: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) - - let ready = Promise() - strongSelf.messageContextDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in - if let strongController = controller { - strongController.dismiss() - } - })) - - (strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready) - } - }) - } - } - } - (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) - }) - } - - func deleteMessages(_ messageIds: Set) { - if !messageIds.isEmpty { - self.messageContextDisposable.set((combineLatest(self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds), self.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] actions, peer in - if let strongSelf = self, let peer = peer, !actions.options.isEmpty { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - var personalPeerName: String? - var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = user.compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if actions.options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - } else if let personalPeerName = personalPeerName { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 - } else { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone - } - items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() - } - })) - } - if actions.options.contains(.deleteLocally) { - var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - if strongSelf.context.account.peerId == strongSelf.peerId { - if messageIds.count == 1 { - localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages - } - } - items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() - } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - } - })) - } - } -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { - let controller: ViewController - weak var sourceNode: ASDisplayNode? - - let navigationController: NavigationController? = nil - - let passthroughTouches: Bool = false - - init(controller: ViewController, sourceNode: ASDisplayNode?) { - self.controller = controller - self.sourceNode = sourceNode - } - - func transitionInfo() -> ContextControllerTakeControllerInfo? { - let sourceNode = self.sourceNode - return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in - if let sourceNode = sourceNode { - return (sourceNode, sourceNode.bounds) - } else { - return nil - } - }) - } - - func animatedIn() { - self.controller.didAppearInContextPreview() - } -} diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift deleted file mode 100644 index ac5bd47f2e..0000000000 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift +++ /dev/null @@ -1,546 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Postbox -import SwiftSignalKit -import Display -import TelegramCore -import SyncCore -import TelegramPresentationData -import AccountContext -import SearchBarNode -import SearchUI -import ChatListSearchItemNode - -struct PeerMediaCollectionMessageForGallery { - let message: Message - let fromSearchResults: Bool -} - -private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: AccountContext, theme: PresentationTheme, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>) -> ChatHistoryNode & ASDisplayNode { - switch mode { - case .photoOrVideo: - let node = ChatHistoryGridNode(context: context, peerId: peerId, messageId: messageId, tagMask: .photoOrVideo, controllerInteraction: controllerInteraction) - node.showVerticalScrollIndicator = true - if theme.list.plainBackgroundColor.argb == 0xffffffff { - node.indicatorStyle = .default - } else { - node.indicatorStyle = .white - } - return node - case .file: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - case .music: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - case .webpage: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - } -} - -private func updateLoadNodeState(_ node: PeerMediaCollectionEmptyNode, _ loadState: ChatHistoryNodeLoadState?) { - if let loadState = loadState { - switch loadState { - case .messages: - node.isHidden = true - node.isLoading = false - case .empty: - node.isHidden = false - node.isLoading = false - case .loading: - node.isHidden = false - node.isLoading = true - } - } else { - node.isHidden = false - node.isLoading = true - } -} - -private func tagMaskForMode(_ mode: PeerMediaCollectionMode) -> MessageTags { - switch mode { - case .photoOrVideo: - return .photoOrVideo - case .file: - return .file - case .music: - return .music - case .webpage: - return .webPage - } -} - -class PeerMediaCollectionControllerNode: ASDisplayNode { - private let context: AccountContext - private let peerId: PeerId - private let controllerInteraction: ChatControllerInteraction - private let interfaceInteraction: ChatPanelInterfaceInteraction - private let navigationBar: NavigationBar? - - private let sectionsNode: PeerMediaCollectionSectionsNode - - private(set) var historyNode: ChatHistoryNode & ASDisplayNode - private var historyEmptyNode: PeerMediaCollectionEmptyNode - - private(set) var searchDisplayController: SearchDisplayController? - - private let candidateHistoryNodeReadyDisposable = MetaDisposable() - private var candidateHistoryNode: (ASDisplayNode, PeerMediaCollectionMode)? - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var requestLayout: (ContainedViewLayoutTransition) -> Void = { _ in } - var requestUpdateMediaCollectionInterfaceState: (Bool, (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) -> Void = { _, _ in } - let requestDeactivateSearch: () -> Void - - private var mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState - - private let selectedMessagesPromise = Promise?>(nil) - var selectedMessages: Set? { - didSet { - if self.selectedMessages != oldValue { - self.selectedMessagesPromise.set(.single(self.selectedMessages)) - } - } - } - private var selectionPanel: ChatMessageSelectionInputPanelNode? - private var selectionPanelSeparatorNode: ASDisplayNode? - private var selectionPanelBackgroundNode: ASDisplayNode? - - private var chatPresentationInterfaceState: ChatPresentationInterfaceState - - private var presentationData: PresentationData - - init(context: AccountContext, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction, navigationBar: NavigationBar?, requestDeactivateSearch: @escaping () -> Void) { - self.context = context - self.peerId = peerId - self.controllerInteraction = controllerInteraction - self.interfaceInteraction = interfaceInteraction - self.navigationBar = navigationBar - - self.requestDeactivateSearch = requestDeactivateSearch - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.mediaCollectionInterfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) - - self.sectionsNode = PeerMediaCollectionSectionsNode(theme: self.presentationData.theme, strings: self.presentationData.strings) - - self.historyNode = historyNodeImplForMode(self.mediaCollectionInterfaceState.mode, context: context, theme: self.presentationData.theme, peerId: peerId, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) - self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) - self.historyEmptyNode.isHidden = true - - self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: self.presentationData.listsFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId), isScheduledMessages: false, peerNearbyData: nil) - - super.init() - - self.setViewBlock({ - return UITracingLayerView() - }) - - self.historyNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - - self.addSubnode(self.historyNode) - self.addSubnode(self.historyEmptyNode) - if let navigationBar = navigationBar { - self.addSubnode(navigationBar) - } - if let navigationBar = self.navigationBar { - self.insertSubnode(self.sectionsNode, aboveSubnode: navigationBar) - } else { - self.addSubnode(self.sectionsNode) - } - - self.sectionsNode.indexUpdated = { [weak self] index in - if let strongSelf = self { - let mode: PeerMediaCollectionMode - switch index { - case 0: - mode = .photoOrVideo - case 1: - mode = .file - case 2: - mode = .webpage - case 3: - mode = .music - default: - mode = .photoOrVideo - } - strongSelf.requestUpdateMediaCollectionInterfaceState(true, { $0.withMode(mode) }) - } - } - - updateLoadNodeState(self.historyEmptyNode, self.historyNode.loadState) - self.historyNode.setLoadStateUpdated { [weak self] loadState, _ in - if let strongSelf = self { - updateLoadNodeState(strongSelf.historyEmptyNode, loadState) - } - } - } - - deinit { - self.candidateHistoryNodeReadyDisposable.dispose() - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeightAndPrimaryHeight: (CGFloat, CGFloat), transition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets) -> Void) { - let navigationBarHeight = navigationBarHeightAndPrimaryHeight.0 - let primaryNavigationBarHeight = navigationBarHeightAndPrimaryHeight.1 - let navigationBarHeightDelta = (navigationBarHeight - primaryNavigationBarHeight) - - self.containerLayout = (layout, navigationBarHeight) - - var vanillaInsets = layout.insets(options: []) - vanillaInsets.top += navigationBarHeight - - var additionalInset: CGFloat = 0.0 - - if (navigationBarHeight - (layout.statusBarHeight ?? 0.0)).isLessThanOrEqualTo(44.0) { - } else { - additionalInset += 10.0 - } - - if let searchDisplayController = self.searchDisplayController { - searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - if !searchDisplayController.isDeactivating { - vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta - } - } - - let sectionsHeight = self.sectionsNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalInset: additionalInset, transition: transition, interfaceState: self.mediaCollectionInterfaceState) - var sectionOffset: CGFloat = 0.0 - if primaryNavigationBarHeight.isZero { - sectionOffset = -sectionsHeight - navigationBarHeightDelta - } else { - //layout.statusBarHeight ?? 0.0 - //if navigationBarHeightAndPrimaryHeight.0 > navigationBarHeightAndPrimaryHeight.1 { - // sectionOffset += 1.0 - //}// - } - transition.updateFrame(node: self.sectionsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + sectionOffset), size: CGSize(width: layout.size.width, height: sectionsHeight))) - - var insets = vanillaInsets - if !primaryNavigationBarHeight.isZero { - insets.top += sectionsHeight - } - - if let inputHeight = layout.inputHeight { - insets.bottom += inputHeight - } - - if let selectionState = self.mediaCollectionInterfaceState.selectionState { - let interfaceState = self.chatPresentationInterfaceState.updatedPeer({ _ in self.mediaCollectionInterfaceState.peer.flatMap(RenderedPeer.init) }) - - if let selectionPanel = self.selectionPanel { - selectionPanel.selectedMessages = selectionState.selectedIds - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics) - transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) - if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { - transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - } - if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { - transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) - } - } else { - let selectionPanelBackgroundNode = ASDisplayNode() - selectionPanelBackgroundNode.isLayerBacked = true - selectionPanelBackgroundNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelBackgroundColor - self.addSubnode(selectionPanelBackgroundNode) - self.selectionPanelBackgroundNode = selectionPanelBackgroundNode - - let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, peerMedia: true) - selectionPanel.context = self.context - selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor - selectionPanel.interfaceInteraction = self.interfaceInteraction - selectionPanel.selectedMessages = selectionState.selectedIds - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: .immediate, interfaceState: interfaceState, metrics: layout.metrics) - self.selectionPanel = selectionPanel - self.addSubnode(selectionPanel) - - let selectionPanelSeparatorNode = ASDisplayNode() - selectionPanelSeparatorNode.isLayerBacked = true - selectionPanelSeparatorNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelSeparatorColor - self.addSubnode(selectionPanelSeparatorNode) - self.selectionPanelSeparatorNode = selectionPanelSeparatorNode - - selectionPanel.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)) - selectionPanelBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: 0.0)) - selectionPanelSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: UIScreenPixel)) - transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) - transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) - transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - } - } else if let selectionPanel = self.selectionPanel { - self.selectionPanel = nil - transition.updateFrame(node: selectionPanel, frame: selectionPanel.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanel] _ in - selectionPanel?.removeFromSupernode() - }) - if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { - transition.updateFrame(node: selectionPanelSeparatorNode, frame: selectionPanelSeparatorNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in - selectionPanelSeparatorNode?.removeFromSupernode() - }) - } - if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { - transition.updateFrame(node: selectionPanelBackgroundNode, frame: selectionPanelBackgroundNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in - selectionPanelSeparatorNode?.removeFromSupernode() - }) - } - } - - let previousBounds = self.historyNode.bounds - self.historyNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) - self.historyNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - self.historyNode.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor - self.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor - - self.historyEmptyNode.updateLayout(size: layout.size, insets: vanillaInsets, transition: transition, interfaceState: mediaCollectionInterfaceState) - transition.updateFrame(node: self.historyEmptyNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - var additionalBottomInset: CGFloat = 0.0 - if let selectionPanel = self.selectionPanel { - additionalBottomInset = selectionPanel.bounds.size.height - } - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - listViewTransaction(ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: - insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.right), duration: duration, curve: curve)) - - if let (candidateHistoryNode, _) = self.candidateHistoryNode { - let previousBounds = candidateHistoryNode.bounds - candidateHistoryNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) - candidateHistoryNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - (candidateHistoryNode as! ChatHistoryNode).updateLayout(transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: - insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.left), duration: duration, curve: curve)) - } - } - - func activateSearch() { - guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else { - return - } - - var maybePlaceholderNode: SearchBarPlaceholderNode? - if let listNode = historyNode as? ListView { - listNode.forEachItemNode { node in - if let node = node as? ChatListSearchItemNode { - maybePlaceholderNode = node.searchBarNode - } - } - } - - if let _ = self.searchDisplayController { - return - } - - if let placeholderNode = maybePlaceholderNode { - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMaskForMode(self.mediaCollectionInterfaceState.mode), interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in - self?.requestDeactivateSearch() - }) - - self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) - self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in - if let strongSelf = self { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) - } - }, placeholder: placeholderNode) - } - - } - - func deactivateSearch() { - if let searchDisplayController = self.searchDisplayController { - self.searchDisplayController = nil - var maybePlaceholderNode: SearchBarPlaceholderNode? - if let listNode = self.historyNode as? ListView { - listNode.forEachItemNode { node in - if let node = node as? ChatListSearchItemNode { - maybePlaceholderNode = node.searchBarNode - } - } - } - - searchDisplayController.deactivate(placeholder: maybePlaceholderNode) - } - } - - func updateMediaCollectionInterfaceState(_ mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState, animated: Bool) { - if self.mediaCollectionInterfaceState != mediaCollectionInterfaceState { - if self.mediaCollectionInterfaceState.mode != mediaCollectionInterfaceState.mode { - let previousMode = self.mediaCollectionInterfaceState.mode - if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode { - let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, context: self.context, theme: self.presentationData.theme, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) - node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor - self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) - - var vanillaInsets = containerLayout.0.insets(options: []) - vanillaInsets.top += containerLayout.1 - - if let searchDisplayController = self.searchDisplayController { - if !searchDisplayController.isDeactivating { - vanillaInsets.top += containerLayout.0.statusBarHeight ?? 0.0 - } - } - - var insets = vanillaInsets - - if !containerLayout.1.isZero { - insets.top += self.sectionsNode.bounds.size.height - } - - if let inputHeight = containerLayout.0.inputHeight { - insets.bottom += inputHeight - } - - let previousBounds = node.bounds - node.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: containerLayout.0.size.width, height: containerLayout.0.size.height) - node.position = CGPoint(x: containerLayout.0.size.width / 2.0, y: containerLayout.0.size.height / 2.0) - - var additionalBottomInset: CGFloat = 0.0 - if let selectionPanel = self.selectionPanel { - additionalBottomInset = selectionPanel.bounds.size.height - } - - node.updateLayout(transition: .immediate, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: containerLayout.0.size, insets: UIEdgeInsets(top: insets.top, left: insets.right + containerLayout.0.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + containerLayout.0.safeInsets.left), duration: 0.0, curve: .Default(duration: nil))) - - let historyEmptyNode = PeerMediaCollectionEmptyNode(mode: mediaCollectionInterfaceState.mode, theme: self.mediaCollectionInterfaceState.theme, strings: self.mediaCollectionInterfaceState.strings) - historyEmptyNode.isHidden = true - historyEmptyNode.updateLayout(size: containerLayout.0.size, insets: vanillaInsets, transition: .immediate, interfaceState: self.mediaCollectionInterfaceState) - historyEmptyNode.frame = CGRect(origin: CGPoint(), size: containerLayout.0.size) - - self.candidateHistoryNodeReadyDisposable.set((node.historyState.get() - |> deliverOnMainQueue).start(next: { [weak self, weak node] _ in - if let strongSelf = self, let strongNode = node, strongNode == strongSelf.candidateHistoryNode?.0 { - strongSelf.candidateHistoryNode = nil - strongSelf.insertSubnode(strongNode, belowSubnode: strongSelf.historyNode) - strongSelf.insertSubnode(historyEmptyNode, aboveSubnode: strongNode) - - let previousNode = strongSelf.historyNode - let previousEmptyNode = strongSelf.historyEmptyNode - strongSelf.historyNode = strongNode - strongSelf.historyEmptyNode = historyEmptyNode - updateLoadNodeState(strongSelf.historyEmptyNode, strongSelf.historyNode.loadState) - strongSelf.historyNode.setLoadStateUpdated { loadState, _ in - if let strongSelf = self { - updateLoadNodeState(strongSelf.historyEmptyNode, loadState) - } - } - - let directionMultiplier: CGFloat - if previousMode.rawValue < mediaCollectionInterfaceState.mode.rawValue { - directionMultiplier = 1.0 - } else { - directionMultiplier = -1.0 - } - - previousNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousNode] _ in - previousNode?.removeFromSupernode() - }) - previousEmptyNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousEmptyNode] _ in - previousEmptyNode?.removeFromSupernode() - }) - strongSelf.historyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - strongSelf.historyEmptyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - })) - } - } - - self.mediaCollectionInterfaceState = mediaCollectionInterfaceState - - self.requestLayout(animated ? .animated(duration: 0.4, curve: .spring) : .immediate) - } - } - - func updateHiddenMedia() { - self.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? ListMessageNode { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateHiddenMedia() - } - } - - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - searchContentNode.updateHiddenMedia() - } - } - - func messageForGallery(_ id: MessageId) -> PeerMediaCollectionMessageForGallery? { - if let message = self.historyNode.messageInCurrentHistoryView(id) { - return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: false) - } - - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - if let message = searchContentNode.messageForGallery(id) { - return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: true) - } - } - - return nil - } - - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - if let transitionNode = searchContentNode.transitionNodeForGallery(messageId: messageId, media: media) { - return transitionNode - } - } - - var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - self.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } else if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } else if let itemNode = itemNode as? GridMessageItemNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } - } - if let transitionNode = transitionNode { - return transitionNode - } - - return nil - } - - func clearHighlightAnimated(_ animated: Bool) { - if let listView = self.historyNode as? ListView { - listView.clearHighlightAnimated(animated) - } - } -} diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3692f55492..4e73e39020 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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 { diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift index 506e7f90cb..b5e386a3a8 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift @@ -55,7 +55,6 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl { fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:] fileprivate var profileDataPreloadContexts: [PeerId: ProfileDataPreloadContext] = [:] fileprivate var profileDataPhotoPreloadContexts: [PeerId: ProfileDataPhotoPreloadContext] = [:] - fileprivate var replyThreadHistoryContexts: [MessageId: ReplyThreadHistoryContext] = [:] func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { if let current = self.contexts[peerId] { @@ -262,17 +261,6 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl { } } } - - func replyThread(account: Account, messageId: MessageId) -> Signal { - let context: ReplyThreadHistoryContext - if let current = self.replyThreadHistoryContexts[messageId] { - context = current - } else { - context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId) - self.replyThreadHistoryContexts[messageId] = context - } - return context.state - } } public final class PeerChannelMemberCategoriesContextsManager { @@ -577,21 +565,4 @@ public final class PeerChannelMemberCategoriesContextsManager { } |> runOn(Queue.mainQueue()) } - - public func replyThread(account: Account, messageId: MessageId) -> Signal { - return Signal { [weak self] subscriber in - guard let strongSelf = self else { - subscriber.putCompletion() - return EmptyDisposable - } - let disposable = MetaDisposable() - strongSelf.impl.with { impl in - disposable.set(impl.replyThread(account: account, messageId: messageId).start(next: { state in - subscriber.putNext(state) - })) - } - return disposable - } - |> runOn(Queue.mainQueue()) - } } From 8e9a0da24d1310d9b62a4bbfbfe60da0c6f866c7 Mon Sep 17 00:00:00 2001 From: overtake Date: Thu, 3 Sep 2020 11:22:55 +0300 Subject: [PATCH 5/5] - fix groups forward. --- .../TelegramCore/Sources/AccountViewTracker.swift | 2 +- .../TelegramCore/Sources/ApplyUpdateMessage.swift | 15 ++++++++------- .../Sources/PendingMessageManager.swift | 2 +- .../TelegramCore/Sources/SearchMessages.swift | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index 806788f325..6aeb2c821f 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -601,7 +601,7 @@ public final class AccountViewTracker { return .single(nil) } |> mapToSignal { result -> Signal in - guard case let .messageViews(viewCounts, _) = result else { + guard case let .messageViews(viewCounts, _)? = result else { return .complete() } diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index 22f7080114..be58039f8e 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -301,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 { diff --git a/submodules/TelegramCore/Sources/PendingMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessageManager.swift index e31f52c341..500e9f32e8 100644 --- a/submodules/TelegramCore/Sources/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessageManager.swift @@ -704,7 +704,7 @@ public final class PendingMessageManager { let sendMessageRequest: Signal if isForward { - if !messages.contains(where: { $0.0.groupingKey == nil }) { + if messages.contains(where: { $0.0.groupingKey != nil }) { flags |= (1 << 9) } diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index 04dd095a19..09e0635727 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?) case group(PeerGroupId) - case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?) + case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?) case publicForwards(messageId: MessageId, datacenterId: Int?) } @@ -180,7 +180,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): + case let .peer(peerId, fromId, tags, topMsgId): if peerId.namespace == Namespaces.Peer.SecretChat { return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var readStates: [PeerId: CombinedPeerReadState] = [:]