Updated comments

This commit is contained in:
Ali 2020-09-01 22:20:55 +01:00
parent 0b48fe6a52
commit 1f6311e414
49 changed files with 1013 additions and 2465 deletions

View File

@ -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<ChatLocationContextHolder?>) -> ChatLocationInput
func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex)
}

View File

@ -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 {

View File

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

View File

@ -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<PeerId>()
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<PeerId>()
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

View File

@ -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<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.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)

View File

@ -485,19 +485,6 @@ final class ViewTracker {
subscriber.putNext(MessageHistoryHolesView(self.messageHistoryHolesView))
}
}
var firstExternalHolesAndTags = Set<MessageHistoryExternalHolesViewEntry>()
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() {

View File

@ -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")
}
}
}

View File

@ -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:

View File

@ -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

View File

@ -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<Api.messages.Messages>) {
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<Api.messages.Messages>) {
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<Api.messages.Messages>) {
public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.DiscussionMessage>) {
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<Api.Bool>) {
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<Api.messages.Messages>) {
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<Api.Bool>) {

View File

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

View File

@ -102,7 +102,8 @@ private final class HistoryPreloadEntry: Comparable {
|> mapToSignal { download -> Signal<Never, NoError> 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
}
}
)

View File

@ -42,8 +42,8 @@ enum FetchMessageHistoryHoleSource {
}
}
func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> Void) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Signal<Never, NoError> in
func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal<T, NoError> {
return postbox.transaction { transaction -> Signal<T, NoError> in
var storedIds = Set<MessageId>()
var referencedIds = Set<MessageId>()
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<Never, NoError> in
|> mapToSignal { results -> Signal<T, NoError> 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<Never, NoError> {
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal<IndexSet, NoError> {
let count = min(100, rawCount)
return postbox.stateView()
@ -139,7 +137,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
}
}
|> take(1)
|> mapToSignal { _ -> Signal<Never, NoError> in
|> mapToSignal { _ -> Signal<IndexSet, NoError> 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<Never, NoError> in
|> mapToSignal { result -> Signal<IndexSet, NoError> 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<MessageId.Id>
@ -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
}
}

View File

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

View File

@ -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<State>()
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<Never, NoError> {
let messageId = self.messageId
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<IndexSet, NoError> {
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<Never, NoError> 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<Api.messages.Messages?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> 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<Api.Bool, NoError> 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<MessageIndex?, NoError> {
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<ChatReplyThreadMessage?, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<MessageIndex?, NoError> in
|> mapToSignal { inputPeer -> Signal<ChatReplyThreadMessage?, NoError> 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<Api.messages.Messages?, NoError> in
|> `catch` { _ -> Signal<Api.messages.DiscussionMessage?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<MessageIndex?, NoError> in
|> mapToSignal { result -> Signal<ChatReplyThreadMessage?, NoError> 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)
)
}
}
}

View File

@ -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<int> 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 {

View File

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

View File

@ -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 {

View File

@ -299,14 +299,44 @@ public final class AccountContextImpl: AccountContext {
}
}
public func chatLocationInput(for location: ChatLocation) -> ChatLocationInput {
public func chatLocationInput(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> 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<ChatLocationContextHolder?>, 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<ChatLocationContextHolder?>, 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 {

View File

@ -46,7 +46,11 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? {
}
}
} else {
return nil
if isMuted {
return .unmuteNotifications
} else {
return .muteNotifications
}
}
}

View File

@ -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<ChatLocationContextHolder?>(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<MessageIndex?>()
let foundIndex = Promise<ChatReplyThreadMessage?>()
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<Bool, NoError> 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
}

View File

@ -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<ChatLocationContextHolder?>, 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

View File

@ -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<UITouch>, 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<UITouch>, 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<UInt64>()
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<Int>()
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<UInt64>()
for i in 0 ..< previousView.filteredEntries.count {
fromStableIds.insert(previousView.filteredEntries[i].stableId)
}
var index = 0
var indices = Set<Int>()
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<ChatHistoryNodeHistoryState>()
private var currentHistoryState: ChatHistoryNodeHistoryState?
public var preloadPages: Bool = true {
didSet {
if self.preloadPages != oldValue {
}
}
}
private let _chatHistoryLocation = ValuePromise<ChatHistoryLocation>(ignoreRepeated: true)
private var chatHistoryLocation: Signal<ChatHistoryLocation, NoError> {
return self._chatHistoryLocation.get()
}
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
private var presentationData: PresentationData
private let chatPresentationDataPromise = Promise<ChatPresentationData>()
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<ChatHistoryView?>(value: nil)
let historyViewTransition = combineLatest(queue: messageViewQueue, historyViewUpdate, self.chatPresentationDataPromise.get())
|> mapToQueue { [weak self] update, chatPresentationData -> Signal<ChatHistoryGridViewTransition, NoError> 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<Void, NoError> 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<Void, NoError> {
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
}
}
}

View File

@ -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<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles) {
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, 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
}*/

View File

@ -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<ChatHistoryViewUpdate, NoError> {
return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics)
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(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<ChatHistoryViewUpdate, Bool> 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<ChatHistoryViewUpdate, NoError> {
func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal<ChatHistoryViewUpdate, NoError> {
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)

View File

@ -5,6 +5,3 @@ import SyncCore
import Display
import AccountContext
func peerSharedMediaControllerImpl(context: AccountContext, peerId: PeerId) -> ViewController? {
return PeerMediaCollectionController(context: context, peerId: peerId)
}

View File

@ -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<MessageIndex?>()
let foundIndex = Promise<ChatReplyThreadMessage?>()
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)
}
})

View File

@ -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

View File

@ -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

View File

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

View File

@ -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]
}

View File

@ -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
}
}

View File

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

View File

@ -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
}

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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<ChatLocationContextHolder?>(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<ChatLocationContextHolder?>(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

View File

@ -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<ChatLocationContextHolder?>(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 {

View File

@ -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

View File

@ -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<Bool>()
private var didSetPeerReady = false
private let peer = Promise<Peer?>(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<MessageId>()
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<ResolvedUrl, NoError>
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<MessageId>) {
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<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
guard let id = id else {
return nil
}
return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|> mapToSignal { status, _ -> Signal<Bool, NoError> 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<Bool>()
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<MessageId>) {
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()
}
}

View File

@ -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<Set<MessageId>?, 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<Set<MessageId>?>(nil)
var selectedMessages: Set<MessageId>? {
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)
}
}
}

View File

@ -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 {

View File

@ -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<MessageHistoryViewExternalInput, NoError> {
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<MessageHistoryViewExternalInput, NoError> {
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())
}
}