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()) - } }