mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Updated comments
This commit is contained in:
parent
0b48fe6a52
commit
1f6311e414
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
98
submodules/Postbox/Sources/MessageHistoryThreadsTable.swift
Normal file
98
submodules/Postbox/Sources/MessageHistoryThreadsTable.swift
Normal 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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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() {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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>) {
|
||||
|
@ -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))
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -46,7 +46,11 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
if isMuted {
|
||||
return .unmuteNotifications
|
||||
} else {
|
||||
return .muteNotifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}*/
|
||||
|
@ -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)
|
||||
|
||||
|
@ -5,6 +5,3 @@ import SyncCore
|
||||
import Display
|
||||
import AccountContext
|
||||
|
||||
func peerSharedMediaControllerImpl(context: AccountContext, peerId: PeerId) -> ViewController? {
|
||||
return PeerMediaCollectionController(context: context, peerId: peerId)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user