Comments update [skip ci]

This commit is contained in:
Ali 2020-09-18 19:17:48 +04:00
parent c378d634c5
commit 0eccec10ed
53 changed files with 5307 additions and 4435 deletions

View File

@ -5790,3 +5790,7 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.TitleComments_1" = "%@ Comment"; "Conversation.TitleComments_1" = "%@ Comment";
"Conversation.TitleComments_any" = "%@ Comments"; "Conversation.TitleComments_any" = "%@ Comments";
"Conversation.TitleNoComments" = "Comments"; "Conversation.TitleNoComments" = "Comments";
"Conversation.ContextMenuBlock" = "Block User";
"Replies.BlockAndDeleteRepliesActionTitle" = "Block and Delete Replies";

View File

@ -170,7 +170,7 @@ public enum ResolvedUrl {
case botStart(peerId: PeerId, payload: String) case botStart(peerId: PeerId, payload: String)
case groupBotStart(peerId: PeerId, payload: String) case groupBotStart(peerId: PeerId, payload: String)
case channelMessage(peerId: PeerId, messageId: MessageId) case channelMessage(peerId: PeerId, messageId: MessageId)
case replyThreadMessage(replyThreadMessageId: MessageId, isChannelPost: Bool, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?, messageId: MessageId) case replyThreadMessage(replyThreadMessageId: MessageId, isChannelPost: Bool, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, messageId: MessageId)
case stickerPack(name: String) case stickerPack(name: String)
case instantView(TelegramMediaWebpage, String?) case instantView(TelegramMediaWebpage, String?)
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
@ -253,7 +253,7 @@ public enum ChatSearchDomain: Equatable {
public enum ChatLocation: Equatable { public enum ChatLocation: Equatable {
case peer(PeerId) case peer(PeerId)
case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?) case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?)
} }
public final class NavigateToChatControllerParams { public final class NavigateToChatControllerParams {
@ -674,5 +674,6 @@ public protocol AccountContext: class {
func getStoredSecureIdPassword() -> String? func getStoredSecureIdPassword() -> String?
func chatLocationInput(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> ChatLocationInput func chatLocationInput(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> ChatLocationInput
func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<MessageId?, NoError>
func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex) func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex)
} }

View File

@ -985,6 +985,9 @@ public final class ChatListNode: ListView {
var cachedResult: [PeerId: [(Peer, PeerInputActivity)]] = [:] var cachedResult: [PeerId: [(Peer, PeerInputActivity)]] = [:]
previousPeerCache.with { dict -> Void in previousPeerCache.with { dict -> Void in
for (chatPeerId, activities) in activitiesByPeerId { for (chatPeerId, activities) in activitiesByPeerId {
if chatPeerId.threadId != nil {
continue
}
var cachedChatResult: [(Peer, PeerInputActivity)] = [] var cachedChatResult: [(Peer, PeerInputActivity)] = []
for (peerId, activity) in activities { for (peerId, activity) in activities {
if let peer = dict[peerId] { if let peer = dict[peerId] {
@ -993,7 +996,7 @@ public final class ChatListNode: ListView {
foundAllPeers = false foundAllPeers = false
break break
} }
cachedResult[chatPeerId] = cachedChatResult cachedResult[chatPeerId.peerId] = cachedChatResult
} }
} }
} }
@ -1004,6 +1007,9 @@ public final class ChatListNode: ListView {
var result: [PeerId: [(Peer, PeerInputActivity)]] = [:] var result: [PeerId: [(Peer, PeerInputActivity)]] = [:]
var peerCache: [PeerId: Peer] = [:] var peerCache: [PeerId: Peer] = [:]
for (chatPeerId, activities) in activitiesByPeerId { for (chatPeerId, activities) in activitiesByPeerId {
if chatPeerId.threadId != nil {
continue
}
var chatResult: [(Peer, PeerInputActivity)] = [] var chatResult: [(Peer, PeerInputActivity)] = []
for (peerId, activity) in activities { for (peerId, activity) in activities {
@ -1013,7 +1019,7 @@ public final class ChatListNode: ListView {
} }
} }
result[chatPeerId] = chatResult result[chatPeerId.peerId] = chatResult
} }
let _ = previousPeerCache.swap(peerCache) let _ = previousPeerCache.swap(peerCache)
return result return result

View File

@ -558,6 +558,10 @@ public final class Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
} }
public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds)
}
func withUpdatedAssociatedMessages(_ associatedMessages: SimpleDictionary<MessageId, Message>) -> Message { func withUpdatedAssociatedMessages(_ associatedMessages: SimpleDictionary<MessageId, Message>) -> Message {
return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds)
} }

View File

@ -465,6 +465,11 @@ final class MessageHistoryTable: Table {
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia) self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
} }
func removeAllMessagesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
let indices = self.allIndicesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
}
func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) { func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
var operations: [MessageHistoryIndexOperation] = [] var operations: [MessageHistoryIndexOperation] = []
self.messageHistoryIndexTable.updateMessage(id, message: self.internalStoreMessages([message]).first!, operations: &operations) self.messageHistoryIndexTable.updateMessage(id, message: self.internalStoreMessages([message]).first!, operations: &operations)
@ -2071,6 +2076,33 @@ final class MessageHistoryTable: Table {
return nil return nil
} }
private func extractIntermediateEntryForwardAuthor(value: ReadBuffer) -> PeerId? {
var type: Int8 = 0
value.read(&type, offset: 0, length: 1)
if type == 0 {
value.skip(4) // stableId
value.skip(4) // stableVersion
var hasGloballyUniqueId: Int8 = 0
value.read(&hasGloballyUniqueId, offset: 0, length: 1)
if hasGloballyUniqueId != 0 {
value.skip(8) // globallyUniqueId
}
value.skip(4) // flags
value.skip(4) // tags
var forwardInfoFlags: Int8 = 0
value.read(&forwardInfoFlags, offset: 0, length: 1)
if forwardInfoFlags != 0 {
var forwardAuthorId: Int64 = 0
value.read(&forwardAuthorId, offset: 0, length: 8)
return PeerId(forwardAuthorId)
}
}
return nil
}
private func readIntermediateEntry(_ key: ValueBoxKey, value: ReadBuffer) -> IntermediateMessageHistoryEntry { private func readIntermediateEntry(_ key: ValueBoxKey, value: ReadBuffer) -> IntermediateMessageHistoryEntry {
let index = extractKey(key) let index = extractKey(key)
@ -2654,6 +2686,17 @@ final class MessageHistoryTable: Table {
return indices return indices
} }
func allIndicesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace) -> [MessageIndex] {
var indices: [MessageIndex] = []
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId, namespace: namespace), end: self.upperBound(peerId: peerId, namespace: namespace), values: { key, value in
if extractIntermediateEntryForwardAuthor(value: value) == forwardAuthorId {
indices.append(extractKey(key))
}
return true
}, limit: 0)
return indices
}
func getMessageCountInRange(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { func getMessageCountInRange(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int {
return self.tagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) return self.tagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound)
} }

View File

@ -251,18 +251,21 @@ public struct MessageHistoryViewOrderStatistics: OptionSet {
public final class MessageHistoryViewExternalInput: Equatable { public final class MessageHistoryViewExternalInput: Equatable {
public let peerId: PeerId public let peerId: PeerId
public let threadId: Int64 public let threadId: Int64
public let maxReadMessageId: MessageId? public let maxReadIncomingMessageId: MessageId?
public let maxReadOutgoingMessageId: MessageId?
public let holes: [MessageId.Namespace: IndexSet] public let holes: [MessageId.Namespace: IndexSet]
public init( public init(
peerId: PeerId, peerId: PeerId,
threadId: Int64, threadId: Int64,
maxReadMessageId: MessageId?, maxReadIncomingMessageId: MessageId?,
maxReadOutgoingMessageId: MessageId?,
holes: [MessageId.Namespace: IndexSet] holes: [MessageId.Namespace: IndexSet]
) { ) {
self.peerId = peerId self.peerId = peerId
self.threadId = threadId self.threadId = threadId
self.maxReadMessageId = maxReadMessageId self.maxReadIncomingMessageId = maxReadIncomingMessageId
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
self.holes = holes self.holes = holes
} }
@ -279,6 +282,12 @@ public final class MessageHistoryViewExternalInput: Equatable {
if lhs.holes != rhs.holes { if lhs.holes != rhs.holes {
return false return false
} }
if lhs.maxReadIncomingMessageId != rhs.maxReadIncomingMessageId {
return false
}
if lhs.maxReadOutgoingMessageId != rhs.maxReadOutgoingMessageId {
return false
}
return true return true
} }
} }
@ -1032,7 +1041,7 @@ public final class MessageHistoryView {
self.maxReadIndex = nil self.maxReadIndex = nil
} }
case let .external(input): case let .external(input):
if let maxReadMesageId = input.maxReadMessageId { if let maxReadMesageId = input.maxReadIncomingMessageId {
var maxIndex: MessageIndex? var maxIndex: MessageIndex?
let hasUnread = true let hasUnread = true

View File

@ -122,6 +122,11 @@ public final class Transaction {
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia) self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia)
} }
public func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
assert(!self.disposed)
self.postbox?.removeAllMessagesWithForwardAuthor(peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, forEachMedia: forEachMedia)
}
public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] { public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] {
assert(!self.disposed) assert(!self.disposed)
if let postbox = self.postbox { if let postbox = self.postbox {
@ -1690,6 +1695,10 @@ public final class Postbox {
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia) self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
} }
fileprivate func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeAllMessagesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
}
fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) { fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {
self.messageHistoryTable.resetIncomingReadStates(states, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) self.messageHistoryTable.resetIncomingReadStates(states, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
} }
@ -2459,7 +2468,7 @@ public final class Postbox {
} }
} }
case let .external(input): case let .external(input):
if let maxReadMessageId = input.maxReadMessageId { if let maxReadMessageId = input.maxReadIncomingMessageId {
anchor = .message(maxReadMessageId) anchor = .message(maxReadMessageId)
} else { } else {
anchor = .upperBound anchor = .upperBound

View File

@ -128,3 +128,39 @@ public final class UnreadMessageCountsView: PostboxView {
return nil return nil
} }
} }
final class MutableCombinedReadStateView: MutablePostboxView {
private let peerId: PeerId
fileprivate var state: CombinedPeerReadState?
init(postbox: Postbox, peerId: PeerId) {
self.peerId = peerId
self.state = postbox.readStateTable.getCombinedState(peerId)
}
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
var updated = false
if transaction.alteredInitialPeerCombinedReadStates[self.peerId] != nil {
let state = postbox.readStateTable.getCombinedState(peerId)
if state != self.state {
self.state = state
updated = true
}
}
return updated
}
func immutableView() -> PostboxView {
return CombinedReadStateView(self)
}
}
public final class CombinedReadStateView: PostboxView {
public let state: CombinedPeerReadState?
init(_ view: MutableCombinedReadStateView) {
self.state = view.state
}
}

View File

@ -15,6 +15,7 @@ public enum PostboxViewKey: Hashable {
case historyTagSummaryView(tag: MessageTags, peerId: PeerId, namespace: MessageId.Namespace) case historyTagSummaryView(tag: MessageTags, peerId: PeerId, namespace: MessageId.Namespace)
case cachedPeerData(peerId: PeerId) case cachedPeerData(peerId: PeerId)
case unreadCounts(items: [UnreadMessageCountsItem]) case unreadCounts(items: [UnreadMessageCountsItem])
case combinedReadState(peerId: PeerId)
case peerNotificationSettings(peerIds: Set<PeerId>) case peerNotificationSettings(peerIds: Set<PeerId>)
case pendingPeerNotificationSettings case pendingPeerNotificationSettings
case messageOfInterestHole(location: MessageOfInterestViewLocation, namespace: MessageId.Namespace, count: Int) case messageOfInterestHole(location: MessageOfInterestViewLocation, namespace: MessageId.Namespace, count: Int)
@ -60,6 +61,8 @@ public enum PostboxViewKey: Hashable {
return peerId.hashValue return peerId.hashValue
case .unreadCounts: case .unreadCounts:
return 5 return 5
case .combinedReadState:
return 16
case .peerNotificationSettings: case .peerNotificationSettings:
return 6 return 6
case .pendingPeerNotificationSettings: case .pendingPeerNotificationSettings:
@ -177,6 +180,12 @@ public enum PostboxViewKey: Hashable {
} else { } else {
return false return false
} }
case let .combinedReadState(peerId):
if case .combinedReadState(peerId) = rhs {
return true
} else {
return false
}
case let .peerNotificationSettings(peerIds): case let .peerNotificationSettings(peerIds):
if case .peerNotificationSettings(peerIds) = rhs { if case .peerNotificationSettings(peerIds) = rhs {
return true return true
@ -295,6 +304,8 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV
return MutableCachedPeerDataView(postbox: postbox, peerId: peerId) return MutableCachedPeerDataView(postbox: postbox, peerId: peerId)
case let .unreadCounts(items): case let .unreadCounts(items):
return MutableUnreadMessageCountsView(postbox: postbox, items: items) return MutableUnreadMessageCountsView(postbox: postbox, items: items)
case let .combinedReadState(peerId):
return MutableCombinedReadStateView(postbox: postbox, peerId: peerId)
case let .peerNotificationSettings(peerIds): case let .peerNotificationSettings(peerIds):
return MutablePeerNotificationSettingsView(postbox: postbox, peerIds: peerIds) return MutablePeerNotificationSettingsView(postbox: postbox, peerIds: peerIds)
case .pendingPeerNotificationSettings: case .pendingPeerNotificationSettings:

View File

@ -255,8 +255,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) }
dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) } dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) }
dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) } dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) }
dict[295679367] = { return Api.Update.parse_updateReadDiscussion($0) } dict[-966672061] = { return Api.Update.parse_updateReadChannelDiscussionInbox($0) }
dict[1178116716] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) }
dict[610945826] = { return Api.Update.parse_updatePeerBlocked($0) } dict[610945826] = { return Api.Update.parse_updatePeerBlocked($0) }
dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@ -305,6 +308,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) } dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) }
dict[-1199954735] = { return Api.MessageReactions.parse_messageReactions($0) }
dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) } dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) }
dict[-2032041631] = { return Api.Poll.parse_poll($0) } dict[-2032041631] = { return Api.Poll.parse_poll($0) }
dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) }
@ -435,7 +439,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) } dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) }
dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) }
dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) }
dict[1835297038] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } dict[-170029155] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) }
dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) } dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) }
dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) } dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) }
dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) }
@ -451,6 +455,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) } dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) } dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) } dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
dict[-1553558980] = { return Api.messages.MessageReactionsList.parse_messageReactionsList($0) }
dict[-1014526429] = { return Api.help.Country.parse_country($0) } dict[-1014526429] = { return Api.help.Country.parse_country($0) }
dict[418631927] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) } dict[418631927] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) }
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) } dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
@ -671,6 +676,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1834973166] = { return Api.BaseTheme.parse_baseThemeTinted($0) } dict[1834973166] = { return Api.BaseTheme.parse_baseThemeTinted($0) }
dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) } dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) }
dict[398898678] = { return Api.help.Support.parse_support($0) } dict[398898678] = { return Api.help.Support.parse_support($0) }
dict[1873957073] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) } dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) }
dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) } dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) }
dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) } dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) }
@ -699,6 +705,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) } dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) }
dict[1984136919] = { return Api.wallet.LiteResponse.parse_liteResponse($0) } dict[1984136919] = { return Api.wallet.LiteResponse.parse_liteResponse($0) }
dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) }
dict[-764945220] = { return Api.MessageUserReaction.parse_messageUserReaction($0) }
dict[-1937807902] = { return Api.BotInlineMessage.parse_botInlineMessageText($0) } dict[-1937807902] = { return Api.BotInlineMessage.parse_botInlineMessageText($0) }
dict[982505656] = { return Api.BotInlineMessage.parse_botInlineMessageMediaGeo($0) } dict[982505656] = { return Api.BotInlineMessage.parse_botInlineMessageMediaGeo($0) }
dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) } dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) }
@ -1052,6 +1059,8 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.FoundGifs: case let _1 as Api.messages.FoundGifs:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.MessageReactions:
_1.serialize(buffer, boxed)
case let _1 as Api.FileLocation: case let _1 as Api.FileLocation:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.Poll: case let _1 as Api.Poll:
@ -1164,6 +1173,8 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DraftMessage: case let _1 as Api.DraftMessage:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.MessageReactionsList:
_1.serialize(buffer, boxed)
case let _1 as Api.help.Country: case let _1 as Api.help.Country:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopPoster: case let _1 as Api.StatsGroupTopPoster:
@ -1388,6 +1399,8 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.help.Support: case let _1 as Api.help.Support:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.ReactionCount:
_1.serialize(buffer, boxed)
case let _1 as Api.MessagesFilter: case let _1 as Api.MessagesFilter:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.Dialogs: case let _1 as Api.messages.Dialogs:
@ -1402,6 +1415,8 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.help.InviteText: case let _1 as Api.help.InviteText:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.MessageUserReaction:
_1.serialize(buffer, boxed)
case let _1 as Api.BotInlineMessage: case let _1 as Api.BotInlineMessage:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.InputPeerNotifySettings: case let _1 as Api.InputPeerNotifySettings:

View File

@ -825,13 +825,13 @@ public struct messages {
} }
public enum DiscussionMessage: TypeConstructorDescription { public enum DiscussionMessage: TypeConstructorDescription {
case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readMaxId: Int32?, chats: [Api.Chat], users: [Api.User]) case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .discussionMessage(let flags, let messages, let maxId, let readMaxId, let chats, let users): case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(1835297038) buffer.appendInt32(-170029155)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -840,7 +840,8 @@ public struct messages {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(readInboxMaxId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(readOutboxMaxId!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count)) buffer.appendInt32(Int32(chats.count))
for item in chats { for item in chats {
@ -857,8 +858,8 @@ public struct messages {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .discussionMessage(let flags, let messages, let maxId, let readMaxId, let chats, let users): case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let chats, let users):
return ("discussionMessage", [("flags", flags), ("messages", messages), ("maxId", maxId), ("readMaxId", readMaxId), ("chats", chats), ("users", users)]) return ("discussionMessage", [("flags", flags), ("messages", messages), ("maxId", maxId), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("chats", chats), ("users", users)])
} }
} }
@ -873,22 +874,87 @@ public struct messages {
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Int32? var _4: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
var _5: [Api.Chat]? var _5: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() }
var _6: [Api.Chat]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
} }
var _6: [Api.User]? var _7: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = _5 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { let _c7 = _7 != nil
return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readMaxId: _4, chats: _5!, users: _6!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, chats: _6!, users: _7!)
}
else {
return nil
}
}
}
public enum MessageReactionsList: TypeConstructorDescription {
case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessageUserReaction], users: [Api.User], nextOffset: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset):
if boxed {
buffer.appendInt32(-1553558980)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reactions.count))
for item in reactions {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset):
return ("messageReactionsList", [("flags", flags), ("count", count), ("reactions", reactions), ("users", users), ("nextOffset", nextOffset)])
}
}
public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.MessageUserReaction]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageUserReaction.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _5: String?
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, users: _4!, nextOffset: _5)
} }
else { else {
return nil return nil
@ -6170,8 +6236,11 @@ public extension Api {
case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) 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 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 updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32)
case updateReadDiscussion(peer: Api.Peer, msgId: Int32, readMaxId: Int32) case updateReadChannelDiscussionInbox(channelId: Int32, topMsgId: Int32, readMaxId: Int32)
case updateReadChannelDiscussionOutbox(channelId: Int32, topMsgId: Int32, readMaxId: Int32)
case updatePeerBlocked(peerId: Api.Peer, blocked: Api.Bool) case updatePeerBlocked(peerId: Api.Peer, blocked: Api.Bool)
case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -6865,12 +6934,20 @@ public extension Api {
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(forwards, buffer: buffer, boxed: false) serializeInt32(forwards, buffer: buffer, boxed: false)
break break
case .updateReadDiscussion(let peer, let msgId, let readMaxId): case .updateReadChannelDiscussionInbox(let channelId, let topMsgId, let readMaxId):
if boxed { if boxed {
buffer.appendInt32(295679367) buffer.appendInt32(-966672061)
} }
peer.serialize(buffer, true) serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt32(topMsgId, buffer: buffer, boxed: false)
serializeInt32(readMaxId, buffer: buffer, boxed: false)
break
case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId):
if boxed {
buffer.appendInt32(1178116716)
}
serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt32(topMsgId, buffer: buffer, boxed: false)
serializeInt32(readMaxId, buffer: buffer, boxed: false) serializeInt32(readMaxId, buffer: buffer, boxed: false)
break break
case .updatePeerBlocked(let peerId, let blocked): case .updatePeerBlocked(let peerId, let blocked):
@ -6880,6 +6957,24 @@ public extension Api {
peerId.serialize(buffer, true) peerId.serialize(buffer, true)
blocked.serialize(buffer, true) blocked.serialize(buffer, true)
break break
case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let userId, let action):
if boxed {
buffer.appendInt32(-13975905)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(channelId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
serializeInt32(userId, buffer: buffer, boxed: false)
action.serialize(buffer, true)
break
case .updateMessageReactions(let peer, let msgId, let reactions):
if boxed {
buffer.appendInt32(357013699)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
} }
} }
@ -7049,10 +7144,16 @@ public extension Api {
return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)]) return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)])
case .updateChannelMessageForwards(let channelId, let id, let forwards): case .updateChannelMessageForwards(let channelId, let id, let forwards):
return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)]) return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)])
case .updateReadDiscussion(let peer, let msgId, let readMaxId): case .updateReadChannelDiscussionInbox(let channelId, let topMsgId, let readMaxId):
return ("updateReadDiscussion", [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]) return ("updateReadChannelDiscussionInbox", [("channelId", channelId), ("topMsgId", topMsgId), ("readMaxId", readMaxId)])
case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId):
return ("updateReadChannelDiscussionOutbox", [("channelId", channelId), ("topMsgId", topMsgId), ("readMaxId", readMaxId)])
case .updatePeerBlocked(let peerId, let blocked): case .updatePeerBlocked(let peerId, let blocked):
return ("updatePeerBlocked", [("peerId", peerId), ("blocked", blocked)]) return ("updatePeerBlocked", [("peerId", peerId), ("blocked", blocked)])
case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let userId, let action):
return ("updateChannelUserTyping", [("flags", flags), ("channelId", channelId), ("topMsgId", topMsgId), ("userId", userId), ("action", action)])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
} }
} }
@ -8432,11 +8533,9 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updateReadDiscussion(_ reader: BufferReader) -> Update? { public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? {
var _1: Api.Peer? var _1: Int32?
if let signature = reader.readInt32() { _1 = reader.readInt32()
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32? var _3: Int32?
@ -8445,7 +8544,24 @@ public extension Api {
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
if _c1 && _c2 && _c3 { if _c1 && _c2 && _c3 {
return Api.Update.updateReadDiscussion(peer: _1!, msgId: _2!, readMaxId: _3!) return Api.Update.updateReadChannelDiscussionInbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!)
}
else {
return nil
}
}
public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
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.updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!)
} }
else { else {
return nil return nil
@ -8469,6 +8585,52 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Int32?
_4 = reader.readInt32()
var _5: Api.SendMessageAction?
if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.SendMessageAction
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.Update.updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, userId: _4!, action: _5!)
}
else {
return nil
}
}
public static func parse_updateMessageReactions(_ 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: Api.MessageReactions?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.MessageReactions
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateMessageReactions(peer: _1!, msgId: _2!, reactions: _3!)
}
else {
return nil
}
}
} }
public enum PopularContact: TypeConstructorDescription { public enum PopularContact: TypeConstructorDescription {
@ -9648,6 +9810,50 @@ public extension Api {
} }
} }
}
public enum MessageReactions: TypeConstructorDescription {
case messageReactions(flags: Int32, results: [Api.ReactionCount])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactions(let flags, let results):
if boxed {
buffer.appendInt32(-1199954735)
}
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(results.count))
for item in results {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactions(let flags, let results):
return ("messageReactions", [("flags", flags), ("results", results)])
}
}
public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.ReactionCount]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageReactions.messageReactions(flags: _1!, results: _2!)
}
else {
return nil
}
}
} }
public enum FileLocation: TypeConstructorDescription { public enum FileLocation: TypeConstructorDescription {
case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32) case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32)
@ -19462,6 +19668,48 @@ public extension Api {
return Api.BaseTheme.baseThemeArctic return Api.BaseTheme.baseThemeArctic
} }
}
public enum ReactionCount: TypeConstructorDescription {
case reactionCount(flags: Int32, reaction: String, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .reactionCount(let flags, let reaction, let count):
if boxed {
buffer.appendInt32(1873957073)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(reaction, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .reactionCount(let flags, let reaction, let count):
return ("reactionCount", [("flags", flags), ("reaction", reaction), ("count", count)])
}
}
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
}
else {
return nil
}
}
} }
public enum MessagesFilter: TypeConstructorDescription { public enum MessagesFilter: TypeConstructorDescription {
case inputMessagesFilterEmpty case inputMessagesFilterEmpty
@ -19774,6 +20022,44 @@ public extension Api {
} }
} }
}
public enum MessageUserReaction: TypeConstructorDescription {
case messageUserReaction(userId: Int32, reaction: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageUserReaction(let userId, let reaction):
if boxed {
buffer.appendInt32(-764945220)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeString(reaction, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageUserReaction(let userId, let reaction):
return ("messageUserReaction", [("userId", userId), ("reaction", reaction)])
}
}
public static func parse_messageUserReaction(_ reader: BufferReader) -> MessageUserReaction? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageUserReaction.messageUserReaction(userId: _1!, reaction: _2!)
}
else {
return nil
}
}
} }
public enum BotInlineMessage: TypeConstructorDescription { public enum BotInlineMessage: TypeConstructorDescription {
case botInlineMessageText(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?) case botInlineMessageText(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?)

View File

@ -1784,21 +1784,6 @@ public extension Api {
}) })
} }
public static func setTyping(peer: Api.InputPeer, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1551737264)
peer.serialize(buffer, true)
action.serialize(buffer, true)
return (FunctionDescription(name: "messages.setTyping", parameters: [("peer", peer), ("action", action)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-820669733) buffer.appendInt32(-820669733)
@ -3777,6 +3762,78 @@ public extension Api {
return result return result
}) })
} }
public static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1486110434)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
action.serialize(buffer, true)
return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", flags), ("peer", peer), ("topMsgId", topMsgId), ("action", action)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(627641572)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(reaction!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("reaction", reaction)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1950707482)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: String?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.MessageReactionsList>) {
let buffer = Buffer()
buffer.appendInt32(-521245833)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(reaction!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", flags), ("peer", peer), ("id", id), ("reaction", reaction), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in
let reader = BufferReader(buffer)
var result: Api.messages.MessageReactionsList?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList
}
return result
})
}
} }
public struct channels { public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {

View File

@ -1186,17 +1186,17 @@ public class Account {
return self.viewTracker.chatHistoryPreloadManager.addAdditionalPeerId(peerId: peerId) return self.viewTracker.chatHistoryPreloadManager.addAdditionalPeerId(peerId: peerId)
} }
public func peerInputActivities(peerId: PeerId) -> Signal<[(PeerId, PeerInputActivity)], NoError> { public func peerInputActivities(peerId: PeerActivitySpace) -> Signal<[(PeerId, PeerInputActivity)], NoError> {
return self.peerInputActivityManager.activities(peerId: peerId) return self.peerInputActivityManager.activities(peerId: peerId)
|> map { activities in |> map { activities in
return activities.map({ ($0.0, $0.1.activity) }) return activities.map({ ($0.0, $0.1.activity) })
} }
} }
public func allPeerInputActivities() -> Signal<[PeerId: [PeerId: PeerInputActivity]], NoError> { public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivity]], NoError> {
return self.peerInputActivityManager.allActivities() return self.peerInputActivityManager.allActivities()
|> map { activities in |> map { activities in
var result: [PeerId: [PeerId: PeerInputActivity]] = [:] var result: [PeerActivitySpace: [PeerId: PeerInputActivity]] = [:]
for (chatPeerId, chatActivities) in activities { for (chatPeerId, chatActivities) in activities {
result[chatPeerId] = chatActivities.mapValues({ $0.activity }) result[chatPeerId] = chatActivities.mapValues({ $0.activity })
} }
@ -1204,7 +1204,7 @@ public class Account {
} }
} }
public func updateLocalInputActivity(peerId: PeerId, activity: PeerInputActivity, isPresent: Bool) { public func updateLocalInputActivity(peerId: PeerActivitySpace, activity: PeerInputActivity, isPresent: Bool) {
self.localInputActivityManager.transaction { manager in self.localInputActivityManager.transaction { manager in
if isPresent { if isPresent {
manager.addActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity) manager.addActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity)
@ -1214,7 +1214,7 @@ public class Account {
} }
} }
public func acquireLocalInputActivity(peerId: PeerId, activity: PeerInputActivity) -> Disposable { public func acquireLocalInputActivity(peerId: PeerActivitySpace, activity: PeerInputActivity) -> Disposable {
return self.localInputActivityManager.acquireActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity) return self.localInputActivityManager.acquireActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity)
} }

View File

@ -90,7 +90,7 @@ enum AccountStateMutationOperation {
case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32)
case AddSecretMessages([Api.EncryptedMessage]) case AddSecretMessages([Api.EncryptedMessage])
case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32)
case AddPeerInputActivity(chatPeerId: PeerId, peerId: PeerId?, activity: PeerInputActivity?) case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?)
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation) case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
case ReadMessageContents((PeerId?, [Int32])) case ReadMessageContents((PeerId?, [Int32]))
case UpdateMessageImpressionCount(MessageId, Int32) case UpdateMessageImpressionCount(MessageId, Int32)
@ -108,6 +108,7 @@ enum AccountStateMutationOperation {
case SyncChatListFilters case SyncChatListFilters
case UpdateChatListFilterOrder(order: [Int32]) case UpdateChatListFilterOrder(order: [Int32])
case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?)
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool)
} }
struct HoleFromPreviousState { struct HoleFromPreviousState {
@ -270,6 +271,10 @@ struct AccountMutableState {
self.addOperation(.ReadOutbox(messageId, timestamp)) self.addOperation(.ReadOutbox(messageId, timestamp))
} }
mutating func readThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool) {
self.addOperation(.UpdateReadThread(threadMessageId: threadMessageId, readMaxId: readMaxId, isIncoming: isIncoming))
}
mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) {
self.addOperation(.ReadGroupFeedInbox(groupId, index)) self.addOperation(.ReadGroupFeedInbox(groupId, index))
} }
@ -420,7 +425,7 @@ struct AccountMutableState {
self.addOperation(.ReadSecretOutbox(peerId: peerId, maxTimestamp: timestamp, actionTimestamp: actionTimestamp)) self.addOperation(.ReadSecretOutbox(peerId: peerId, maxTimestamp: timestamp, actionTimestamp: actionTimestamp))
} }
mutating func addPeerInputActivity(chatPeerId: PeerId, peerId: PeerId?, activity: PeerInputActivity?) { mutating func addPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) {
self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity)) self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity))
} }
@ -474,7 +479,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {
@ -587,19 +592,21 @@ struct AccountReplayedFinalState {
let addedIncomingMessageIds: [MessageId] let addedIncomingMessageIds: [MessageId]
let wasScheduledMessageIds: [MessageId] let wasScheduledMessageIds: [MessageId]
let addedSecretMessageIds: [MessageId] let addedSecretMessageIds: [MessageId]
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]]
let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall] let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)] let addedCallSignalingData: [(Int64, Data)]
let updatedPeersNearby: [PeerNearby]? let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, Bool)] let isContactUpdates: [(PeerId, Bool)]
let delayNotificatonsUntil: Int32? let delayNotificatonsUntil: Int32?
let updatedIncomingThreadReadStates: [MessageId: MessageId.Id]
let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id]
} }
struct AccountFinalStateEvents { struct AccountFinalStateEvents {
let addedIncomingMessageIds: [MessageId] let addedIncomingMessageIds: [MessageId]
let wasScheduledMessageIds:[MessageId] let wasScheduledMessageIds:[MessageId]
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]]
let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall] let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)] let addedCallSignalingData: [(Int64, Data)]
@ -611,12 +618,14 @@ struct AccountFinalStateEvents {
let updatedQts: Int32? let updatedQts: Int32?
let externallyUpdatedPeerId: Set<PeerId> let externallyUpdatedPeerId: Set<PeerId>
let authorizationListUpdated: Bool let authorizationListUpdated: Bool
let updatedIncomingThreadReadStates: [MessageId: MessageId.Id]
let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id]
var isEmpty: Bool { var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty
} }
init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false) { init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) {
self.addedIncomingMessageIds = addedIncomingMessageIds self.addedIncomingMessageIds = addedIncomingMessageIds
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
self.updatedTypingActivities = updatedTypingActivities self.updatedTypingActivities = updatedTypingActivities
@ -631,6 +640,8 @@ struct AccountFinalStateEvents {
self.updatedQts = updatedQts self.updatedQts = updatedQts
self.externallyUpdatedPeerId = externallyUpdatedPeerId self.externallyUpdatedPeerId = externallyUpdatedPeerId
self.authorizationListUpdated = authorizationListUpdated self.authorizationListUpdated = authorizationListUpdated
self.updatedIncomingThreadReadStates = updatedIncomingThreadReadStates
self.updatedOutgoingThreadReadStates = updatedOutgoingThreadReadStates
} }
init(state: AccountReplayedFinalState) { init(state: AccountReplayedFinalState) {
@ -648,6 +659,8 @@ struct AccountFinalStateEvents {
self.updatedQts = state.state.state.updatedQts self.updatedQts = state.state.state.updatedQts
self.externallyUpdatedPeerId = state.state.state.externallyUpdatedPeerId self.externallyUpdatedPeerId = state.state.state.externallyUpdatedPeerId
self.authorizationListUpdated = state.state.state.authorizationListUpdated self.authorizationListUpdated = state.state.state.authorizationListUpdated
self.updatedIncomingThreadReadStates = state.updatedIncomingThreadReadStates
self.updatedOutgoingThreadReadStates = state.updatedOutgoingThreadReadStates
} }
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -673,6 +686,6 @@ struct AccountFinalStateEvents {
let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId) let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId)
let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated) return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }))
} }
} }

View File

@ -1206,16 +1206,21 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.readSecretOutbox(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), timestamp: maxDate, actionTimestamp: date) updatedState.readSecretOutbox(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), timestamp: maxDate, actionTimestamp: date)
case let .updateUserTyping(userId, type): case let .updateUserTyping(userId, type):
if let date = updatesDate, date + 60 > serverTime { if let date = updatesDate, date + 60 > serverTime {
updatedState.addPeerInputActivity(chatPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type)) updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), threadId: nil), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
} }
case let .updateChatUserTyping(chatId, userId, type): case let .updateChatUserTyping(chatId, userId, type):
if let date = updatesDate, date + 60 > serverTime { if let date = updatesDate, date + 60 > serverTime {
updatedState.addPeerInputActivity(chatPeerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type)) updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId), threadId: nil), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
updatedState.addPeerInputActivity(chatPeerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: chatId), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type)) }
case let .updateChannelUserTyping(_, channelId, topMsgId, userId, type):
if let date = updatesDate, date + 60 > serverTime {
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
let threadId = topMsgId.flatMap { makeMessageThreadId(MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: $0)) }
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: channelPeerId, threadId: threadId), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
} }
case let .updateEncryptedChatTyping(chatId): case let .updateEncryptedChatTyping(chatId):
if let date = updatesDate, date + 60 > serverTime { if let date = updatesDate, date + 60 > serverTime {
updatedState.addPeerInputActivity(chatPeerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), peerId: nil, activity: .typingText) updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), threadId: nil), peerId: nil, activity: .typingText)
} }
case let .updateDialogPinned(flags, folderId, peer): case let .updateDialogPinned(flags, folderId, peer):
let groupId: PeerGroupId = folderId.flatMap(PeerGroupId.init(rawValue:)) ?? .root let groupId: PeerGroupId = folderId.flatMap(PeerGroupId.init(rawValue:)) ?? .root
@ -2094,7 +2099,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState? var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -2174,7 +2179,9 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
var peerIdsWithAddedSecretMessages = Set<PeerId>() var peerIdsWithAddedSecretMessages = Set<PeerId>()
var updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] = [:] var updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:]
var updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:]
var updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]
var updatedSecretChatTypingActivities = Set<PeerId>() var updatedSecretChatTypingActivities = Set<PeerId>()
var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:]
var updatedCalls: [Api.PhoneCall] = [] var updatedCalls: [Api.PhoneCall] = []
@ -2318,10 +2325,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
let chatPeerId = message.id.peerId let chatPeerId = message.id.peerId
if let authorId = message.authorId { if let authorId = message.authorId {
let activityValue: PeerInputActivity? = nil let activityValue: PeerInputActivity? = nil
if updatedTypingActivities[chatPeerId] == nil { if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
updatedTypingActivities[chatPeerId] = [authorId: activityValue] updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
} else { } else {
updatedTypingActivities[chatPeerId]![authorId] = activityValue updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
} }
} }
@ -2528,6 +2535,24 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
case let .ReadGroupFeedInbox(groupId, index): case let .ReadGroupFeedInbox(groupId, index):
break break
//transaction.applyGroupFeedReadMaxIndex(groupId: groupId, index: index) //transaction.applyGroupFeedReadMaxIndex(groupId: groupId, index: index)
case let .UpdateReadThread(threadMessageId, readMaxId, isIncoming):
if isIncoming {
if let currentId = updatedIncomingThreadReadStates[threadMessageId] {
if currentId < readMaxId {
updatedIncomingThreadReadStates[threadMessageId] = readMaxId
}
} else {
updatedIncomingThreadReadStates[threadMessageId] = readMaxId
}
} else {
if let currentId = updatedOutgoingThreadReadStates[threadMessageId] {
if currentId < readMaxId {
updatedOutgoingThreadReadStates[threadMessageId] = readMaxId
}
} else {
updatedOutgoingThreadReadStates[threadMessageId] = readMaxId
}
}
case let .ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count, markedUnread): case let .ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count, markedUnread):
var markedUnreadValue: Bool = false var markedUnreadValue: Bool = false
if let markedUnread = markedUnread { if let markedUnread = markedUnread {
@ -2750,8 +2775,8 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
} else { } else {
updatedTypingActivities[chatPeerId]![peerId] = activity updatedTypingActivities[chatPeerId]![peerId] = activity
} }
} else if chatPeerId.namespace == Namespaces.Peer.SecretChat { } else if chatPeerId.peerId.namespace == Namespaces.Peer.SecretChat {
updatedSecretChatTypingActivities.insert(chatPeerId) updatedSecretChatTypingActivities.insert(chatPeerId.peerId)
} }
case let .UpdatePinnedItemIds(groupId, pinnedOperation): case let .UpdatePinnedItemIds(groupId, pinnedOperation):
switch pinnedOperation { switch pinnedOperation {
@ -3132,10 +3157,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
if let peer = transaction.getPeer(chatPeerId) as? TelegramSecretChat { if let peer = transaction.getPeer(chatPeerId) as? TelegramSecretChat {
let authorId = peer.regularPeerId let authorId = peer.regularPeerId
let activityValue: PeerInputActivity? = .typingText let activityValue: PeerInputActivity? = .typingText
if updatedTypingActivities[chatPeerId] == nil { if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
updatedTypingActivities[chatPeerId] = [authorId: activityValue] updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
} else { } else {
updatedTypingActivities[chatPeerId]![authorId] = activityValue updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
} }
} }
} }
@ -3180,10 +3205,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
for (chatPeerId, authorId) in addedSecretMessageAuthorIds { for (chatPeerId, authorId) in addedSecretMessageAuthorIds {
let activityValue: PeerInputActivity? = nil let activityValue: PeerInputActivity? = nil
if updatedTypingActivities[chatPeerId] == nil { if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
updatedTypingActivities[chatPeerId] = [authorId: activityValue] updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
} else { } else {
updatedTypingActivities[chatPeerId]![authorId] = activityValue updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
} }
} }
@ -3260,5 +3285,5 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
requestChatListFiltersSync(transaction: transaction) requestChatListFiltersSync(transaction: transaction)
} }
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil) return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates)
} }

View File

@ -138,6 +138,11 @@ public final class AccountStateManager {
return self.authorizationListUpdatesPipe.signal() return self.authorizationListUpdatesPipe.signal()
} }
private let threadReadStateUpdatesPipe = ValuePipe<(incoming: [MessageId: MessageId.Id], outgoing: [MessageId: MessageId.Id])>()
var threadReadStateUpdates: Signal<(incoming: [MessageId: MessageId.Id], outgoing: [MessageId: MessageId.Id]), NoError> {
return self.threadReadStateUpdatesPipe.signal()
}
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:] private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext() private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
@ -658,6 +663,9 @@ public final class AccountStateManager {
strongSelf.callSessionManager.addCallSignalingData(id: id, data: data) strongSelf.callSessionManager.addCallSignalingData(id: id, data: data)
} }
} }
if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty {
strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates))
}
if !events.isContactUpdates.isEmpty { if !events.isContactUpdates.isEmpty {
strongSelf.addIsContactUpdates(events.isContactUpdates) strongSelf.addIsContactUpdates(events.isContactUpdates)
} }

View File

@ -1187,7 +1187,14 @@ public final class AccountViewTracker {
} }
}) })
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { let peerId: PeerId
switch chatLocation {
case let .peer(peerIdValue):
peerId = peerIdValue
case let .external(peerIdValue, _):
peerId = peerIdValue
}
if peerId.namespace == Namespaces.Peer.CloudChannel {
return Signal { subscriber in return Signal { subscriber in
let combinedDisposable = MetaDisposable() let combinedDisposable = MetaDisposable()
self.queue.async { self.queue.async {
@ -1203,7 +1210,7 @@ public final class AccountViewTracker {
let _ = self.account?.postbox.transaction({ transaction -> Void in let _ = self.account?.postbox.transaction({ transaction -> Void in
if transaction.getPeerChatListIndex(peerId) == nil { if transaction.getPeerChatListIndex(peerId) == nil {
if let message = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud) { if let message = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud) {
transaction.addHole(peerId: peerId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: message.id + 1 ... (Int32.max - 1)) //transaction.addHole(peerId: peerId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: message.id + 1 ... (Int32.max - 1))
} }
} }
}).start() }).start()

View File

@ -67,6 +67,16 @@ public func deleteAllMessagesWithAuthor(transaction: Transaction, mediaBox: Medi
} }
} }
public func deleteAllMessagesWithForwardAuthor(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace) {
var resourceIds: [WrappedMediaResourceId] = []
transaction.removeAllMessagesWithForwardAuthor(peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, forEachMedia: { media in
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
})
if !resourceIds.isEmpty {
let _ = mediaBox.removeCachedResources(Set(resourceIds)).start()
}
}
public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, namespaces: MessageIdNamespaces) { public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, namespaces: MessageIdNamespaces) {
if peerId.namespace == Namespaces.Peer.SecretChat { if peerId.namespace == Namespaces.Peer.SecretChat {
var resourceIds: [WrappedMediaResourceId] = [] var resourceIds: [WrappedMediaResourceId] = []

View File

@ -6,19 +6,29 @@ import MtProtoKit
import SyncCore import SyncCore
public struct PeerActivitySpace: Hashable {
public var peerId: PeerId
public var threadId: Int64?
public init(peerId: PeerId, threadId: Int64?) {
self.peerId = peerId
self.threadId = threadId
}
}
struct PeerInputActivityRecord: Equatable { struct PeerInputActivityRecord: Equatable {
let activity: PeerInputActivity let activity: PeerInputActivity
let updateId: Int32 let updateId: Int32
} }
private final class ManagedLocalTypingActivitiesContext { private final class ManagedLocalTypingActivitiesContext {
private var disposables: [PeerId: (PeerInputActivityRecord, MetaDisposable)] = [:] private var disposables: [PeerActivitySpace: (PeerInputActivityRecord, MetaDisposable)] = [:]
func update(activities: [PeerId: [PeerId: PeerInputActivityRecord]]) -> (start: [(PeerId, PeerInputActivityRecord?, MetaDisposable)], dispose: [MetaDisposable]) { func update(activities: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> (start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)], dispose: [MetaDisposable]) {
var start: [(PeerId, PeerInputActivityRecord?, MetaDisposable)] = [] var start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)] = []
var dispose: [MetaDisposable] = [] var dispose: [MetaDisposable] = []
var validPeerIds = Set<PeerId>() var validPeerIds = Set<PeerActivitySpace>()
for (peerId, record) in activities { for (peerId, record) in activities {
if let activity = record.values.first { if let activity = record.values.first {
validPeerIds.insert(peerId) validPeerIds.insert(peerId)
@ -37,7 +47,7 @@ private final class ManagedLocalTypingActivitiesContext {
} }
} }
var removePeerIds: [PeerId] = [] var removePeerIds: [PeerActivitySpace] = []
for key in self.disposables.keys { for key in self.disposables.keys {
if !validPeerIds.contains(key) { if !validPeerIds.contains(key) {
removePeerIds.append(key) removePeerIds.append(key)
@ -60,7 +70,7 @@ private final class ManagedLocalTypingActivitiesContext {
} }
} }
func managedLocalTypingActivities(activities: Signal<[PeerId: [PeerId: PeerInputActivityRecord]], NoError>, postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> { func managedLocalTypingActivities(activities: Signal<[PeerActivitySpace: [PeerId: PeerInputActivityRecord]], NoError>, postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
return Signal { subscriber in return Signal { subscriber in
let context = Atomic(value: ManagedLocalTypingActivitiesContext()) let context = Atomic(value: ManagedLocalTypingActivitiesContext())
let disposable = activities.start(next: { activities in let disposable = activities.start(next: { activities in
@ -73,7 +83,7 @@ func managedLocalTypingActivities(activities: Signal<[PeerId: [PeerId: PeerInput
} }
for (peerId, activity, disposable) in start { for (peerId, activity, disposable) in start {
disposable.set(requestActivity(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, activity: activity?.activity).start()) disposable.set(requestActivity(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId.peerId, threadId: peerId.threadId, activity: activity?.activity).start())
} }
}) })
return ActionDisposable { return ActionDisposable {
@ -111,7 +121,7 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa
} }
} }
private func requestActivity(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, activity: PeerInputActivity?) -> Signal<Void, NoError> { private func requestActivity(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadId: Int64?, activity: PeerInputActivity?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in return postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId) { if let peer = transaction.getPeer(peerId) {
if peerId == accountPeerId { if peerId == accountPeerId {
@ -122,7 +132,12 @@ private func requestActivity(postbox: Postbox, network: Network, accountPeerId:
} }
if let inputPeer = apiInputPeer(peer) { if let inputPeer = apiInputPeer(peer) {
return network.request(Api.functions.messages.setTyping(peer: inputPeer, action: actionFromActivity(activity))) var flags: Int32 = 0
let topMessageId = threadId.flatMap { makeThreadIdMessageId(peerId: peerId, threadId: $0) }
if topMessageId != nil {
flags |= 1 << 0
}
return network.request(Api.functions.messages.setTyping(flags: flags, peer: inputPeer, topMsgId: topMessageId?.id, action: actionFromActivity(activity)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
} }

View File

@ -178,9 +178,9 @@ private final class PeerInputActivityContext {
} }
private final class PeerGlobalInputActivityContext { private final class PeerGlobalInputActivityContext {
private let subscribers = Bag<([PeerId: [PeerId: PeerInputActivityRecord]]) -> Void>() private let subscribers = Bag<([PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> Void>()
func addSubscriber(_ subscriber: @escaping ([PeerId: [PeerId: PeerInputActivityRecord]]) -> Void) -> Int { func addSubscriber(_ subscriber: @escaping ([PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> Void) -> Int {
return self.subscribers.add(subscriber) return self.subscribers.add(subscriber)
} }
@ -192,7 +192,7 @@ private final class PeerGlobalInputActivityContext {
return self.subscribers.isEmpty return self.subscribers.isEmpty
} }
func notify(_ activities: [PeerId: [PeerId: PeerInputActivityRecord]]) { func notify(_ activities: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) {
for subscriber in self.subscribers.copyItems() { for subscriber in self.subscribers.copyItems() {
subscriber(activities) subscriber(activities)
} }
@ -204,10 +204,10 @@ final class PeerInputActivityManager {
private var nextEpisodeId: Int32 = 0 private var nextEpisodeId: Int32 = 0
private var nextUpdateId: Int32 = 0 private var nextUpdateId: Int32 = 0
private var contexts: [PeerId: PeerInputActivityContext] = [:] private var contexts: [PeerActivitySpace: PeerInputActivityContext] = [:]
private var globalContext: PeerGlobalInputActivityContext? private var globalContext: PeerGlobalInputActivityContext?
func activities(peerId: PeerId) -> Signal<[(PeerId, PeerInputActivityRecord)], NoError> { func activities(peerId: PeerActivitySpace) -> Signal<[(PeerId, PeerInputActivityRecord)], NoError> {
let queue = self.queue let queue = self.queue
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
@ -256,10 +256,10 @@ final class PeerInputActivityManager {
} }
} }
private func collectActivities() -> [PeerId: [PeerId: PeerInputActivityRecord]] { private func collectActivities() -> [PeerActivitySpace: [PeerId: PeerInputActivityRecord]] {
assert(self.queue.isCurrent()) assert(self.queue.isCurrent())
var dict: [PeerId: [PeerId: PeerInputActivityRecord]] = [:] var dict: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]] = [:]
for (chatPeerId, context) in self.contexts { for (chatPeerId, context) in self.contexts {
var chatDict: [PeerId: PeerInputActivityRecord] = [:] var chatDict: [PeerId: PeerInputActivityRecord] = [:]
for (peerId, activity) in context.topActivities() { for (peerId, activity) in context.topActivities() {
@ -270,7 +270,7 @@ final class PeerInputActivityManager {
return dict return dict
} }
func allActivities() -> Signal<[PeerId: [PeerId: PeerInputActivityRecord]], NoError> { func allActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivityRecord]], NoError> {
let queue = self.queue let queue = self.queue
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
@ -306,7 +306,7 @@ final class PeerInputActivityManager {
} }
} }
func addActivity(chatPeerId: PeerId, peerId: PeerId, activity: PeerInputActivity, episodeId: Int32? = nil) { func addActivity(chatPeerId: PeerActivitySpace, peerId: PeerId, activity: PeerInputActivity, episodeId: Int32? = nil) {
self.queue.async { self.queue.async {
let context: PeerInputActivityContext let context: PeerInputActivityContext
if let currentContext = self.contexts[chatPeerId] { if let currentContext = self.contexts[chatPeerId] {
@ -338,7 +338,7 @@ final class PeerInputActivityManager {
} }
} }
func removeActivity(chatPeerId: PeerId, peerId: PeerId, activity: PeerInputActivity, episodeId: Int32? = nil) { func removeActivity(chatPeerId: PeerActivitySpace, peerId: PeerId, activity: PeerInputActivity, episodeId: Int32? = nil) {
self.queue.async { self.queue.async {
if let context = self.contexts[chatPeerId] { if let context = self.contexts[chatPeerId] {
context.removeActivity(peerId: peerId, activity: activity, episodeId: episodeId) context.removeActivity(peerId: peerId, activity: activity, episodeId: episodeId)
@ -351,7 +351,7 @@ final class PeerInputActivityManager {
} }
} }
func removeAllActivities(chatPeerId: PeerId, peerId: PeerId) { func removeAllActivities(chatPeerId: PeerActivitySpace, peerId: PeerId) {
self.queue.async { self.queue.async {
if let currentContext = self.contexts[chatPeerId] { if let currentContext = self.contexts[chatPeerId] {
currentContext.removeAllActivities(peerId: peerId) currentContext.removeAllActivities(peerId: peerId)
@ -370,7 +370,7 @@ final class PeerInputActivityManager {
} }
} }
func acquireActivity(chatPeerId: PeerId, peerId: PeerId, activity: PeerInputActivity) -> Disposable { func acquireActivity(chatPeerId: PeerActivitySpace, peerId: PeerId, activity: PeerInputActivity) -> Disposable {
let disposable = MetaDisposable() let disposable = MetaDisposable()
let queue = self.queue let queue = self.queue
queue.async { queue.async {

View File

@ -41,6 +41,7 @@ private final class PendingMessageContext {
var state: PendingMessageState = .none var state: PendingMessageState = .none
let uploadDisposable = MetaDisposable() let uploadDisposable = MetaDisposable()
let sendDisposable = MetaDisposable() let sendDisposable = MetaDisposable()
var threadId: Int64?
var activityType: PeerInputActivity? = nil var activityType: PeerInputActivity? = nil
var contentType: PendingMessageUploadedContentType? = nil var contentType: PendingMessageUploadedContentType? = nil
let activityDisposable = MetaDisposable() let activityDisposable = MetaDisposable()
@ -371,6 +372,7 @@ public final class PendingMessageManager {
} }
messageContext.activityType = uploadActivityTypeForMessage(message) messageContext.activityType = uploadActivityTypeForMessage(message)
messageContext.threadId = message.threadId
strongSelf.collectUploadingInfo(messageContext: messageContext, message: message) strongSelf.collectUploadingInfo(messageContext: messageContext, message: message)
} }
@ -425,7 +427,7 @@ public final class PendingMessageManager {
for (messageContext, message, type, contentUploadSignal) in messagesToUpload { for (messageContext, message, type, contentUploadSignal) in messagesToUpload {
if strongSelf.canBeginUploadingMessage(id: message.id, type: type) { if strongSelf.canBeginUploadingMessage(id: message.id, type: type) {
strongSelf.beginUploadingMessage(messageContext: messageContext, id: message.id, groupId: message.groupingKey, uploadSignal: contentUploadSignal) strongSelf.beginUploadingMessage(messageContext: messageContext, id: message.id, threadId: message.threadId, groupId: message.groupingKey, uploadSignal: contentUploadSignal)
} else { } else {
messageContext.state = .waitingForUploadToStart(groupId: message.groupingKey, upload: contentUploadSignal) messageContext.state = .waitingForUploadToStart(groupId: message.groupingKey, upload: contentUploadSignal)
} }
@ -544,7 +546,7 @@ public final class PendingMessageManager {
messageContext.state = .collectingInfo(message: message) messageContext.state = .collectingInfo(message: message)
} }
private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, groupId: Int64?, uploadSignal: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>) { private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, threadId: Int64?, groupId: Int64?, uploadSignal: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>) {
messageContext.state = .uploading(groupId: groupId) messageContext.state = .uploading(groupId: groupId)
let status = PendingMessageStatus(isRunning: true, progress: 0.0) let status = PendingMessageStatus(isRunning: true, progress: 0.0)
@ -552,7 +554,7 @@ public final class PendingMessageManager {
for subscriber in messageContext.statusSubscribers.copyItems() { for subscriber in messageContext.statusSubscribers.copyItems() {
subscriber(messageContext.status, messageContext.error) subscriber(messageContext.status, messageContext.error)
} }
self.addContextActivityIfNeeded(messageContext, peerId: id.peerId) self.addContextActivityIfNeeded(messageContext, peerId: PeerActivitySpace(peerId: id.peerId, threadId: threadId))
let queue = self.queue let queue = self.queue
@ -602,7 +604,7 @@ public final class PendingMessageManager {
})) }))
} }
private func addContextActivityIfNeeded(_ context: PendingMessageContext, peerId: PeerId) { private func addContextActivityIfNeeded(_ context: PendingMessageContext, peerId: PeerActivitySpace) {
if let activityType = context.activityType { if let activityType = context.activityType {
context.activityDisposable.set(self.localInputActivityManager.acquireActivity(chatPeerId: peerId, peerId: self.accountPeerId, activity: activityType)) context.activityDisposable.set(self.localInputActivityManager.acquireActivity(chatPeerId: peerId, peerId: self.accountPeerId, activity: activityType))
} }
@ -622,7 +624,7 @@ public final class PendingMessageManager {
for subscriber in context.statusSubscribers.copyItems() { for subscriber in context.statusSubscribers.copyItems() {
subscriber(context.status, context.error) subscriber(context.status, context.error)
} }
self.addContextActivityIfNeeded(context, peerId: peerId) self.addContextActivityIfNeeded(context, peerId: PeerActivitySpace(peerId: peerId, threadId: context.threadId))
context.uploadDisposable.set((uploadSignal context.uploadDisposable.set((uploadSignal
|> deliverOn(self.queue)).start(next: { [weak self] next in |> deliverOn(self.queue)).start(next: { [weak self] next in
if let strongSelf = self { if let strongSelf = self {

View File

@ -14,7 +14,8 @@ private class ReplyThreadHistoryContextImpl {
struct State: Equatable { struct State: Equatable {
var messageId: MessageId var messageId: MessageId
var holeIndices: [MessageId.Namespace: IndexSet] var holeIndices: [MessageId.Namespace: IndexSet]
var maxReadMessageId: MessageId? var maxReadIncomingMessageId: MessageId?
var maxReadOutgoingMessageId: MessageId?
} }
let state = Promise<State>() let state = Promise<State>()
@ -28,15 +29,28 @@ private class ReplyThreadHistoryContextImpl {
} }
} }
let maxReadOutgoingMessageId = Promise<MessageId?>()
private var maxReadOutgoingMessageIdValue: MessageId? {
didSet {
if self.maxReadOutgoingMessageIdValue != oldValue {
self.maxReadOutgoingMessageId.set(.single(nil))
}
}
}
private var initialStateDisposable: Disposable? private var initialStateDisposable: Disposable?
private var holesDisposable: Disposable? private var holesDisposable: Disposable?
private var readStateDisposable: Disposable?
private let readDisposable = MetaDisposable() private let readDisposable = MetaDisposable()
init(queue: Queue, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?) { init(queue: Queue, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
self.queue = queue self.queue = queue
self.account = account self.account = account
self.messageId = messageId self.messageId = messageId
self.maxReadOutgoingMessageIdValue = maxReadOutgoingMessageId
self.maxReadOutgoingMessageId.set(.single(self.maxReadOutgoingMessageIdValue))
self.initialStateDisposable = (account.postbox.transaction { transaction -> State in self.initialStateDisposable = (account.postbox.transaction { transaction -> State in
var indices = transaction.getThreadIndexHoles(peerId: messageId.peerId, threadId: makeMessageThreadId(messageId), namespace: Namespaces.Message.Cloud) var indices = transaction.getThreadIndexHoles(peerId: messageId.peerId, threadId: makeMessageThreadId(messageId), namespace: Namespaces.Message.Cloud)
switch maxMessage { switch maxMessage {
@ -57,7 +71,7 @@ private class ReplyThreadHistoryContextImpl {
indices = IndexSet() indices = IndexSet()
}*/ }*/
} }
return State(messageId: messageId, holeIndices: [Namespaces.Message.Cloud: indices], maxReadMessageId: maxReadMessageId) return State(messageId: messageId, holeIndices: [Namespaces.Message.Cloud: indices], maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
} }
|> deliverOn(self.queue)).start(next: { [weak self] state in |> deliverOn(self.queue)).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -88,6 +102,16 @@ private class ReplyThreadHistoryContextImpl {
} }
strongSelf.setCurrentHole(entry: entry) strongSelf.setCurrentHole(entry: entry)
}) })
self.readStateDisposable = (account.stateManager.threadReadStateUpdates
|> deliverOn(self.queue)).start(next: { [weak self] (_, outgoing) in
guard let strongSelf = self else {
return
}
if let value = outgoing[messageId] {
strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud, id: value)
}
})
} }
deinit { deinit {
@ -207,7 +231,8 @@ public class ReplyThreadHistoryContext {
subscriber.putNext(MessageHistoryViewExternalInput( subscriber.putNext(MessageHistoryViewExternalInput(
peerId: state.messageId.peerId, peerId: state.messageId.peerId,
threadId: makeMessageThreadId(state.messageId), threadId: makeMessageThreadId(state.messageId),
maxReadMessageId: state.maxReadMessageId, maxReadIncomingMessageId: state.maxReadIncomingMessageId,
maxReadOutgoingMessageId: state.maxReadOutgoingMessageId,
holes: state.holeIndices holes: state.holeIndices
)) ))
}) })
@ -218,10 +243,24 @@ public class ReplyThreadHistoryContext {
} }
} }
public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?) { public var maxReadOutgoingMessageId: Signal<MessageId?, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.maxReadOutgoingMessageId.get().start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
let queue = self.queue let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId) return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
}) })
} }
@ -240,12 +279,14 @@ public struct ChatReplyThreadMessage {
public var messageId: MessageId public var messageId: MessageId
public var maxMessage: MaxMessage public var maxMessage: MaxMessage
public var maxReadMessageId: MessageId? public var maxReadIncomingMessageId: MessageId?
public var maxReadOutgoingMessageId: MessageId?
public init(messageId: MessageId, maxMessage: MaxMessage, maxReadMessageId: MessageId?) { public init(messageId: MessageId, maxMessage: MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
self.messageId = messageId self.messageId = messageId
self.maxMessage = maxMessage self.maxMessage = maxMessage
self.maxReadMessageId = maxReadMessageId self.maxReadIncomingMessageId = maxReadIncomingMessageId
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
} }
} }
@ -263,42 +304,14 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
return .single(nil) return .single(nil)
} }
let maxMessage: Signal<Int32?, NoError> = account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: [messageId.id], increment: .boolFalse)) return discussionMessage
|> map(Optional.init) |> mapToSignal { discussionMessage -> Signal<ChatReplyThreadMessage?, NoError> in
|> `catch` { _ -> Signal<Api.messages.MessageViews?, NoError> in
return .single(nil)
}
|> map { result -> Int32? in
guard let result = result else {
return nil
}
var maxId: Int32?
switch result {
case let .messageViews(views, _, _):
for view in views {
switch view {
case let .messageViews(_, _, _, replies):
if let replies = replies {
switch replies {
case let .messageReplies(_, _, _, _, _, maxIdValue, readMaxIdValue):
maxId = maxIdValue
}
}
}
}
}
return maxId
}
return combineLatest(discussionMessage, maxMessage)
|> mapToSignal { discussionMessage, maxMessage -> Signal<ChatReplyThreadMessage?, NoError> in
guard let discussionMessage = discussionMessage else { guard let discussionMessage = discussionMessage else {
return .single(nil) return .single(nil)
} }
return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in
switch discussionMessage { switch discussionMessage {
//messages.discussionMessage flags:# messages:Vector<Message> max_id:flags.0?int read_max_id:flags.1?int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage; case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users):
case let .discussionMessage(_, messages, maxId, readMaxId, chats, users):
let parsedMessages = messages.compactMap { message -> StoreMessage? in let parsedMessages = messages.compactMap { message -> StoreMessage? in
StoreMessage(apiMessage: message) StoreMessage(apiMessage: message)
} }
@ -332,11 +345,11 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
let resolvedMaxMessage: ChatReplyThreadMessage.MaxMessage let resolvedMaxMessage: ChatReplyThreadMessage.MaxMessage
if let maxMessage = maxMessage { if let maxId = maxId {
resolvedMaxMessage = .known(MessageId( resolvedMaxMessage = .known(MessageId(
peerId: parsedIndex.id.peerId, peerId: parsedIndex.id.peerId,
namespace: Namespaces.Message.Cloud, namespace: Namespaces.Message.Cloud,
id: maxMessage id: maxId
)) ))
} else { } else {
resolvedMaxMessage = .known(nil) resolvedMaxMessage = .known(nil)
@ -345,7 +358,10 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
return ChatReplyThreadMessage( return ChatReplyThreadMessage(
messageId: parsedIndex.id, messageId: parsedIndex.id,
maxMessage: resolvedMaxMessage, maxMessage: resolvedMaxMessage,
maxReadMessageId: readMaxId.flatMap { readMaxId in maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in
MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId)
},
maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in
MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId)
} }
) )

View File

@ -303,29 +303,39 @@ public final class AccountContextImpl: AccountContext {
switch location { switch location {
case let .peer(peerId): case let .peer(peerId):
return .peer(peerId) return .peer(peerId)
case let .replyThread(messageId, _, maxMessage, maxReadMessageId): case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId):
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId) let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
return .external(messageId.peerId, context.state) return .external(messageId.peerId, context.state)
} }
} }
public func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<MessageId?, NoError> {
switch location {
case .peer:
return .single(nil)
case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId):
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
return context.maxReadOutgoingMessageId
}
}
public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex) { public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex) {
switch location { switch location {
case .peer: case .peer:
let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start() let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start()
case let .replyThread(messageId, _, maxMessage, maxReadMessageId): case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId):
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId) let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
context.applyMaxReadIndex(messageIndex: messageIndex) context.applyMaxReadIndex(messageIndex: messageIndex)
} }
} }
} }
private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?) -> ReplyThreadHistoryContext { private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) -> ReplyThreadHistoryContext {
let holder = holder.modify { current in let holder = holder.modify { current in
if let current = current as? ChatLocationContextHolderImpl { if let current = current as? ChatLocationContextHolderImpl {
return current return current
} else { } else {
return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId) return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
} }
} as! ChatLocationContextHolderImpl } as! ChatLocationContextHolderImpl
return holder.context return holder.context
@ -334,8 +344,8 @@ private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, acc
private final class ChatLocationContextHolderImpl: ChatLocationContextHolder { private final class ChatLocationContextHolderImpl: ChatLocationContextHolder {
let context: ReplyThreadHistoryContext let context: ReplyThreadHistoryContext
init(account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadMessageId: MessageId?) { init(account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId) self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
} }
} }

View File

@ -69,7 +69,7 @@ extension ChatLocation {
switch self { switch self {
case let .peer(peerId): case let .peer(peerId):
return peerId return peerId
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
return messageId.peerId return messageId.peerId
} }
} }
@ -371,7 +371,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .peer(peerId): case let .peer(peerId):
locationBroadcastPanelSource = .peer(peerId) locationBroadcastPanelSource = .peer(peerId)
self.chatLocationInfoData = .peer(Promise()) self.chatLocationInfoData = .peer(Promise())
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
locationBroadcastPanelSource = .none locationBroadcastPanelSource = .none
let promise = Promise<Message?>() let promise = Promise<Message?>()
let key = PostboxViewKey.messages([messageId]) let key = PostboxViewKey.messages([messageId])
@ -480,7 +480,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: strongSelf.chatLocation, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { var openChatLocation = strongSelf.chatLocation
if case let .replyThread(messageId, _, _, _, _) = openChatLocation {
if message.threadId != makeMessageThreadId(messageId) {
openChatLocation = .peer(message.id.peerId)
}
}
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: openChatLocation, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: {
self?.chatDisplayNode.dismissInput() self?.chatDisplayNode.dismissInput()
}, present: { c, a in }, present: { c, a in
self?.present(c, in: .window(.root), with: a, blockInteraction: true) self?.present(c, in: .window(.root), with: a, blockInteraction: true)
@ -1594,6 +1601,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
if let strongSelf = self { if let strongSelf = self {
if case .replyThread(message.id, _, _, _, _) = strongSelf.chatLocation {
return .none
}
if canReplyInChat(strongSelf.presentationInterfaceState) { if canReplyInChat(strongSelf.presentationInterfaceState) {
return .reply return .reply
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
@ -1629,7 +1640,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch strongSelf.chatLocation { switch strongSelf.chatLocation {
case let .peer(peerId): 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) 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 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) 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)
} }
@ -2180,36 +2191,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let foundIndex = Promise<ReplyThreadInfo?>() if let navigationController = strongSelf.navigationController as? NavigationController {
foundIndex.set(fetchAndPreloadReplyThreadInfo(context: strongSelf.context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId))) ChatControllerImpl.openMessageReplies(context: strongSelf.context, navigationController: navigationController, present: { c, a in
self?.present(c, in: .window(.root), with: a)
var cancelImpl: (() -> Void)? }, messageId: messageId, isChannelPost: isChannelPost, atMessage: nil)
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { }
cancelImpl?() }, openReplyThreadOriginalMessage: { [weak self] message in
}))
strongSelf.present(statusController, in: .window(.root))
let disposable = (foundIndex.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
var threadMessageId: MessageId?
if let result = result { for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
threadMessageId = attribute.threadMessageId
break
}
}
for attribute in message.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if let threadMessageId = threadMessageId {
if let navigationController = strongSelf.navigationController as? NavigationController { if let navigationController = strongSelf.navigationController as? NavigationController {
let chatLocation: ChatLocation = .replyThread(threadMessageId: result.message.messageId, isChannelPost: result.isChannelPost, maxMessage: result.message.maxMessage, maxReadMessageId: result.message.maxReadMessageId) ChatControllerImpl.openMessageReplies(context: strongSelf.context, navigationController: navigationController, present: { c, a in
self?.present(c, in: .window(.root), with: a)
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, activateInput: result.isEmpty, keepStack: .always)) }, messageId: threadMessageId, isChannelPost: true, atMessage: attribute.messageId)
} }
} else {
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId))
}
break
} }
})
cancelImpl = { [weak statusController] in
disposable.dispose()
statusController?.dismiss()
} }
}, requestMessageUpdate: { [weak self] id in }, requestMessageUpdate: { [weak self] id in
if let strongSelf = self { if let strongSelf = self {
@ -2641,7 +2651,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch chatLocation { switch chatLocation {
case .peer: case .peer:
replyThreadType = .replies replyThreadType = .replies
case let .replyThread(_, isChannelPost, _, _): case let .replyThread(_, isChannelPost, _, _, _):
isReplyThread = true isReplyThread = true
if isChannelPost { if isChannelPost {
replyThreadType = .comments replyThreadType = .comments
@ -2983,22 +2993,30 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.startBot(botStart.payload) self.startBot(botStart.payload)
} }
let activitySpace: PeerActivitySpace
switch self.chatLocation {
case let .peer(peerId):
activitySpace = PeerActivitySpace(peerId: peerId, threadId: nil)
case let .replyThread(threadMessageId, _, _, _, _):
activitySpace = PeerActivitySpace(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId))
}
self.inputActivityDisposable = (self.typingActivityPromise.get() self.inputActivityDisposable = (self.typingActivityPromise.get()
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { if let strongSelf = self {
strongSelf.context.account.updateLocalInputActivity(peerId: peerId, activity: .typingText, isPresent: value) strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .typingText, isPresent: value)
} }
}) })
self.recordingActivityDisposable = (self.recordingActivityPromise.get() self.recordingActivityDisposable = (self.recordingActivityPromise.get()
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { if let strongSelf = self {
strongSelf.acquiredRecordingActivityDisposable?.dispose() strongSelf.acquiredRecordingActivityDisposable?.dispose()
switch value { switch value {
case .voice: case .voice:
strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: peerId, activity: .recordingVoice) strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: activitySpace, activity: .recordingVoice)
case .instantVideo: case .instantVideo:
strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: peerId, activity: .recordingInstantVideo) strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: activitySpace, activity: .recordingInstantVideo)
case .none: case .none:
strongSelf.acquiredRecordingActivityDisposable = nil strongSelf.acquiredRecordingActivityDisposable = nil
} }
@ -3398,7 +3416,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if let _ = cachedData as? CachedSecretChatData { } else if let _ = cachedData as? CachedSecretChatData {
} }
if case let .replyThread(messageId, _, _, _) = strongSelf.chatLocation { if case let .replyThread(messageId, _, _, _, _) = strongSelf.chatLocation {
if isTopReplyThreadMessageShown { if isTopReplyThreadMessageShown {
pinnedMessageId = nil pinnedMessageId = nil
} else { } else {
@ -3905,6 +3923,58 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self, !messages.isEmpty { if let strongSelf = self, !messages.isEmpty {
presentPeerReportOptions(context: strongSelf.context, parent: strongSelf, contextController: contextController, subject: .messages(messages.map({ $0.id }).sorted()), completion: { _ in }) presentPeerReportOptions(context: strongSelf.context, parent: strongSelf, contextController: contextController, subject: .messages(messages.map({ $0.id }).sorted()), completion: { _ in })
} }
}, blockMessageAuthor: { [weak self] message, contextController in
contextController?.dismiss(completion: {
guard let strongSelf = self else {
return
}
let author = message.forwardInfo?.author
guard let peer = author else {
return
}
let presentationData = strongSelf.presentationData
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
var reportSpam = true
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: presentationData.strings.UserInfo_BlockConfirmationTitle(peer.compactDisplayTitle).0))
items.append(contentsOf: [
ActionSheetCheckboxItem(title: presentationData.strings.Conversation_Moderate_Report, label: "", value: reportSpam, action: { [weak controller] checkValue in
reportSpam = checkValue
controller?.updateItem(groupIndex: 0, itemIndex: 1, { item in
if let item = item as? ActionSheetCheckboxItem {
return ActionSheetCheckboxItem(title: item.title, label: item.label, value: !item.value, action: item.action)
}
return item
})
}),
ActionSheetButtonItem(title: presentationData.strings.Replies_BlockAndDeleteRepliesActionTitle, color: .destructive, action: {
dismissAction()
guard let strongSelf = self else {
return
}
let _ = requestUpdatePeerIsBlocked(account: strongSelf.context.account, peerId: peer.id, isBlocked: true).start()
let account = strongSelf.context.account
let _ = (strongSelf.context.account.postbox.transaction { transasction -> Void in
deleteAllMessagesWithForwardAuthor(transaction: transasction, mediaBox: account.postbox.mediaBox, peerId: message.id.peerId, forwardAuthorId: peer.id, namespace: Namespaces.Message.Cloud)
}).start()
if reportSpam {
let _ = TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peer.id, reason: .spam).start()
}
})
] as [ActionSheetItem])
controller.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
}, deleteMessages: { [weak self] messages, contextController, completion in }, deleteMessages: { [weak self] messages, contextController, completion in
if let strongSelf = self, !messages.isEmpty { if let strongSelf = self, !messages.isEmpty {
let messageIds = Set(messages.map { $0.id }) let messageIds = Set(messages.map { $0.id })
@ -5035,15 +5105,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let navigationController = strongSelf.effectiveNavigationController { if let navigationController = strongSelf.effectiveNavigationController {
let subject: ChatControllerSubject? = sourceMessageId.flatMap(ChatControllerSubject.message) let subject: ChatControllerSubject? = sourceMessageId.flatMap(ChatControllerSubject.message)
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadResult.messageId, isChannelPost: false, maxMessage: replyThreadResult.maxMessage, maxReadMessageId: replyThreadResult.maxReadMessageId), subject: subject, keepStack: .always)) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadResult.messageId, isChannelPost: false, maxMessage: replyThreadResult.maxMessage, maxReadIncomingMessageId: replyThreadResult.maxReadIncomingMessageId, maxReadOutgoingMessageId: replyThreadResult.maxReadOutgoingMessageId), 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())) }, 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 { do {
let peerId = self.chatLocation.peerId let peerId = self.chatLocation.peerId
if let subject = self.subject, case .scheduledMessages = subject { if let subject = self.subject, case .scheduledMessages = subject {
} else if case .replyThread = self.chatLocation {
} else { } else {
if case .peer = self.chatLocation {
let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)])
let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId]))
self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey])
@ -5093,10 +5163,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
}) })
}
let postbox = self.context.account.postbox let postbox = self.context.account.postbox
let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:])
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) var activityThreadId: Int64?
if case let .replyThread(messageId, _, _, _, _) = self.chatLocation {
activityThreadId = makeMessageThreadId(messageId)
}
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, threadId: activityThreadId))
|> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in
var foundAllPeers = true var foundAllPeers = true
var cachedResult: [(Peer, PeerInputActivity)] = [] var cachedResult: [(Peer, PeerInputActivity)] = []
@ -7491,7 +7566,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.chatLocation { switch self.chatLocation {
case .peer: case .peer:
break break
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
defaultReplyMessageId = messageId defaultReplyMessageId = messageId
} }
@ -7538,7 +7613,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.chatLocation { switch self.chatLocation {
case let .peer(peerIdValue): case let .peer(peerIdValue):
peerId = peerIdValue peerId = peerIdValue
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
peerId = messageId.peerId peerId = messageId.peerId
} }
@ -7960,7 +8035,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.chatLocation { switch self.chatLocation {
case .peer: case .peer:
break break
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
searchTopMsgId = messageId searchTopMsgId = messageId
} }
switch search.domain { switch search.domain {
@ -8176,6 +8251,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
} }
static func openMessageReplies(context: AccountContext, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?) {
let foundIndex = Promise<ReplyThreadInfo?>()
foundIndex.set(fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId)))
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var cancelImpl: (() -> Void)?
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
present(statusController, nil)
let disposable = (foundIndex.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
if let result = result {
let chatLocation: ChatLocation = .replyThread(threadMessageId: result.message.messageId, isChannelPost: result.isChannelPost, maxMessage: result.message.maxMessage, maxReadIncomingMessageId: result.message.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.message.maxReadOutgoingMessageId)
let subject: ChatControllerSubject?
if let atMessageId = atMessageId {
subject = .message(atMessageId)
} else {
subject = nil
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty, keepStack: .always))
}
})
cancelImpl = { [weak statusController] in
disposable.dispose()
statusController?.dismiss()
}
}
public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, dropStack: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, dropStack: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) {
let scrollPosition: ListViewScrollPosition let scrollPosition: ListViewScrollPosition
if case .upperBound = messageLocation { if case .upperBound = messageLocation {

View File

@ -113,6 +113,7 @@ public final class ChatControllerInteraction {
let greetingStickerNode: () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)? let greetingStickerNode: () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?
let openPeerContextMenu: (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void let openPeerContextMenu: (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void
let openMessageReplies: (MessageId, Bool) -> Void let openMessageReplies: (MessageId, Bool) -> Void
let openReplyThreadOriginalMessage: (Message) -> Void
let requestMessageUpdate: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void let cancelInteractiveKeyboardGestures: () -> Void
@ -196,6 +197,7 @@ public final class ChatControllerInteraction {
greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?,
openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void,
openMessageReplies: @escaping (MessageId, Bool) -> Void, openMessageReplies: @escaping (MessageId, Bool) -> Void,
openReplyThreadOriginalMessage: @escaping (Message) -> Void,
requestMessageUpdate: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void,
cancelInteractiveKeyboardGestures: @escaping () -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings, automaticMediaDownloadSettings: MediaAutoDownloadSettings,
@ -267,6 +269,7 @@ public final class ChatControllerInteraction {
self.greetingStickerNode = greetingStickerNode self.greetingStickerNode = greetingStickerNode
self.openPeerContextMenu = openPeerContextMenu self.openPeerContextMenu = openPeerContextMenu
self.openMessageReplies = openMessageReplies self.openMessageReplies = openMessageReplies
self.openReplyThreadOriginalMessage = openReplyThreadOriginalMessage
self.requestMessageUpdate = requestMessageUpdate self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -316,6 +319,7 @@ public final class ChatControllerInteraction {
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -8,7 +8,7 @@ import AccountContext
import TelegramPresentationData import TelegramPresentationData
func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set<MessageId>?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia]) -> [ChatHistoryEntry] { func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set<MessageId>?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia], customChannelDiscussionReadState: MessageId?, customThreadOutgoingReadState: MessageId?) -> [ChatHistoryEntry] {
if historyAppearsCleared { if historyAppearsCleared {
return [] return []
} }
@ -31,9 +31,31 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = []
loop: for entry in view.entries { loop: for entry in view.entries {
var message = entry.message
var isRead = entry.isRead
if let customThreadOutgoingReadState = customThreadOutgoingReadState {
isRead = customThreadOutgoingReadState >= message.id
}
if let customChannelDiscussionReadState = customChannelDiscussionReadState {
attibuteLoop: for i in 0 ..< message.attributes.count {
if let attribute = message.attributes[i] as? ReplyThreadMessageAttribute {
if let maxReadMessageId = attribute.maxReadMessageId {
if maxReadMessageId < customChannelDiscussionReadState.id {
var attributes = message.attributes
attributes[i] = ReplyThreadMessageAttribute(count: attribute.count, latestUsers: attribute.latestUsers, commentsPeerId: attribute.commentsPeerId, maxMessageId: attribute.maxMessageId, maxReadMessageId: customChannelDiscussionReadState.id)
message = message.withUpdatedAttributes(attributes)
}
}
break attibuteLoop
}
}
}
var contentTypeHint: ChatMessageEntryContentType = .generic var contentTypeHint: ChatMessageEntryContentType = .generic
for media in entry.message.media { for media in message.media {
if media is TelegramMediaDice { if media is TelegramMediaDice {
contentTypeHint = .animatedEmoji contentTypeHint = .animatedEmoji
} }
@ -48,49 +70,49 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} }
var adminRank: CachedChannelAdminRank? var adminRank: CachedChannelAdminRank?
if let author = entry.message.author { if let author = message.author {
adminRank = adminRanks[author.id] adminRank = adminRanks[author.id]
} }
if presentationData.largeEmoji, entry.message.media.isEmpty { if presentationData.largeEmoji, message.media.isEmpty {
if stickersEnabled && entry.message.text.count == 1, let _ = associatedData.animatedEmojiStickers[entry.message.text.basicEmoji.0] { if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0] {
contentTypeHint = .animatedEmoji contentTypeHint = .animatedEmoji
} else if entry.message.text.count < 10 && messageIsElligibleForLargeEmoji(entry.message) { } else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) {
contentTypeHint = .largeEmoji contentTypeHint = .largeEmoji
} }
} }
if groupMessages { if groupMessages {
if !groupBucket.isEmpty && entry.message.groupInfo != groupBucket[0].0.groupInfo { if !groupBucket.isEmpty && message.groupInfo != groupBucket[0].0.groupInfo {
entries.append(.MessageGroupEntry(groupBucket[0].0.groupInfo!, groupBucket, presentationData)) entries.append(.MessageGroupEntry(groupBucket[0].0.groupInfo!, groupBucket, presentationData))
groupBucket.removeAll() groupBucket.removeAll()
} }
if let _ = entry.message.groupInfo { if let _ = message.groupInfo {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages { if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(entry.message.id)) selection = .selectable(selected: selectedMessages.contains(message.id))
} else { } else {
selection = .none selection = .none
} }
groupBucket.append((entry.message, entry.isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id]))) groupBucket.append((message, entry.isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id])))
} else { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages { if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(entry.message.id)) selection = .selectable(selected: selectedMessages.contains(message.id))
} else { } else {
selection = .none selection = .none
} }
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id]))) entries.append(.MessageEntry(message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id])))
} }
} else { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages { if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(entry.message.id)) selection = .selectable(selected: selectedMessages.contains(message.id))
} else { } else {
selection = .none selection = .none
} }
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id]))) entries.append(.MessageEntry(message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id])))
} }
} }
@ -114,7 +136,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} }
var addedThreadHead = false var addedThreadHead = false
if case let .replyThread(messageId, isChannelPost, _, _) = location, view.earlierId == nil, !view.isLoading { if case let .replyThread(messageId, isChannelPost, _, _, _) = location, view.earlierId == nil, !view.isLoading {
loop: for entry in view.additionalData { loop: for entry in view.additionalData {
switch entry { switch entry {
case let .message(id, messages) where id == messageId: case let .message(id, messages) where id == messageId:

View File

@ -627,7 +627,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if !isAuxiliaryChat { if !isAuxiliaryChat {
additionalData.append(.totalUnreadState) additionalData.append(.totalUnreadState)
} }
if case let .replyThread(messageId, _, _, _) = chatLocation { if case let .replyThread(messageId, _, _, _, _) = chatLocation {
additionalData.append(.cachedPeerData(messageId.peerId)) additionalData.append(.cachedPeerData(messageId.peerId))
additionalData.append(.peerNotificationSettings(messageId.peerId)) additionalData.append(.peerNotificationSettings(messageId.peerId))
if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { if messageId.peerId.namespace == Namespaces.Peer.CloudChannel {
@ -714,6 +714,65 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
|> distinctUntilChanged |> distinctUntilChanged
let customChannelDiscussionReadState: Signal<MessageId?, NoError>
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel {
let cachedDataKey = PostboxViewKey.cachedPeerData(peerId: chatLocation.peerId)
let peerKey = PostboxViewKey.basicPeer(peerId)
customChannelDiscussionReadState = context.account.postbox.combinedView(keys: [cachedDataKey, peerKey])
|> mapToSignal { views -> Signal<PeerId?, NoError> in
guard let view = views.views[cachedDataKey] as? CachedPeerDataView else {
return .single(nil)
}
guard let peer = (views.views[peerKey] as? BasicPeerView)?.peer as? TelegramChannel, case .broadcast = peer.info else {
return .single(nil)
}
guard let cachedData = view.cachedPeerData as? CachedChannelData else {
return .single(nil)
}
guard case let .known(value) = cachedData.linkedDiscussionPeerId else {
return .single(nil)
}
return .single(value)
}
|> distinctUntilChanged
|> mapToSignal { discussionPeerId -> Signal<MessageId?, NoError> in
guard let discussionPeerId = discussionPeerId else {
return .single(nil)
}
let key = PostboxViewKey.combinedReadState(peerId: discussionPeerId)
return context.account.postbox.combinedView(keys: [key])
|> map { views -> MessageId? in
guard let view = views.views[key] as? CombinedReadStateView else {
return nil
}
guard let state = view.state else {
return nil
}
for (namespace, namespaceState) in state.states {
if namespace == Namespaces.Message.Cloud {
switch namespaceState {
case let .idBased(maxIncomingReadId, _, _, _, _):
return MessageId(peerId: discussionPeerId, namespace: Namespaces.Message.Cloud, id: maxIncomingReadId)
default:
break
}
}
}
return nil
}
|> distinctUntilChanged
}
} else {
customChannelDiscussionReadState = .single(nil)
}
let customThreadOutgoingReadState: Signal<MessageId?, NoError>
if case .replyThread = chatLocation {
customThreadOutgoingReadState = context.chatLocationOutgoingReadState(for: chatLocation, contextHolder: chatLocationContextHolder)
} else {
customThreadOutgoingReadState = .single(nil)
}
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate, historyViewUpdate,
self.chatPresentationDataPromise.get(), self.chatPresentationDataPromise.get(),
@ -721,8 +780,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
updatingMedia, updatingMedia,
automaticDownloadNetworkType, automaticDownloadNetworkType,
self.historyAppearsClearedPromise.get(), self.historyAppearsClearedPromise.get(),
animatedEmojiStickers animatedEmojiStickers,
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, animatedEmojiStickers in customChannelDiscussionReadState,
customThreadOutgoingReadState
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState in
func applyHole() { func applyHole() {
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
@ -806,7 +867,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, isScheduledMessages: isScheduledMessages) let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, isScheduledMessages: isScheduledMessages)
let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, associatedData: associatedData, updatingMedia: updatingMedia) let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0 let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id) let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id)
let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages)) let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages))
@ -1112,7 +1173,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if hasUnconsumedMention && !hasUnconsumedContent { if hasUnconsumedMention && !hasUnconsumedContent {
messageIdsWithUnseenPersonalMention.append(message.id) messageIdsWithUnseenPersonalMention.append(message.id)
} }
if case .replyThread(message.id, _, _, _) = self.chatLocation { if case .replyThread(message.id, _, _, _, _) = self.chatLocation {
isTopReplyThreadMessageShownValue = true isTopReplyThreadMessageShownValue = true
} }
case let .MessageGroupEntry(_, messages, _): case let .MessageGroupEntry(_, messages, _):
@ -1142,7 +1203,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if hasUnconsumedMention && !hasUnconsumedContent { if hasUnconsumedMention && !hasUnconsumedContent {
messageIdsWithUnseenPersonalMention.append(message.id) messageIdsWithUnseenPersonalMention.append(message.id)
} }
if case .replyThread(message.id, _, _, _) = self.chatLocation { if case .replyThread(message.id, _, _, _, _) = self.chatLocation {
isTopReplyThreadMessageShownValue = true isTopReplyThreadMessageShownValue = true
} }
} }

View File

@ -308,11 +308,7 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
case let .channelPost(messageId): case let .channelPost(messageId):
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId) message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId)
case let .groupMessage(messageId): case let .groupMessage(messageId):
message = .single(ChatReplyThreadMessage( message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId)
messageId: messageId,
maxMessage: .unknown,
maxReadMessageId: nil
))
} }
return message return message
@ -341,7 +337,8 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
threadMessageId: message.messageId, threadMessageId: message.messageId,
isChannelPost: isChannelPost, isChannelPost: isChannelPost,
maxMessage: message.maxMessage, maxMessage: message.maxMessage,
maxReadMessageId: message.maxReadMessageId maxReadIncomingMessageId: message.maxReadIncomingMessageId,
maxReadOutgoingMessageId: message.maxReadOutgoingMessageId
), ),
chatLocationContextHolder: chatLocationContextHolder, chatLocationContextHolder: chatLocationContextHolder,
fixedCombinedReadStates: nil, fixedCombinedReadStates: nil,

View File

@ -134,6 +134,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
guard !chatPresentationInterfaceState.isScheduledMessages else { guard !chatPresentationInterfaceState.isScheduledMessages else {
return false return false
} }
guard !peer.id.isReplies else {
return false
}
switch chatPresentationInterfaceState.mode { switch chatPresentationInterfaceState.mode {
case .inline: case .inline:
return false return false
@ -465,7 +468,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} }
var isReplyThreadHead = false var isReplyThreadHead = false
if case let .replyThread(messageId, _, _, _) = chatPresentationInterfaceState.chatLocation { if case let .replyThread(messageId, _, _, _, _) = chatPresentationInterfaceState.chatLocation {
isReplyThreadHead = messages[0].id == messageId isReplyThreadHead = messages[0].id == messageId
} }
@ -616,9 +619,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} }
c.dismiss(completion: { c.dismiss(completion: {
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel {
if case .group = channel.info {
interfaceInteraction.viewReplies(messages[0].id, ChatReplyThreadMessage(messageId: replyThreadId, maxMessage: .unknown, maxReadMessageId: nil))
} else {
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?
let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: {
cancelImpl?() cancelImpl?()
@ -640,7 +640,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
statusController?.dismiss() statusController?.dismiss()
} }
} }
}
}) })
}))) })))
} }
@ -746,10 +745,10 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
var threadMessageId: MessageId? var threadMessageId: MessageId?
if case let .replyThread(replyThread, _, _, _) = chatPresentationInterfaceState.chatLocation { if case let .replyThread(replyThread, _, _, _, _) = chatPresentationInterfaceState.chatLocation {
threadMessageId = replyThread threadMessageId = replyThread
} }
let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, threadMessageId: threadMessageId) let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|> map { result -> String? in |> map { result -> String? in
return result return result
} }
@ -838,6 +837,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
}, action: { controller, f in }, action: { controller, f in
interfaceInteraction.reportMessages(selectAll ? messages : [message], controller) interfaceInteraction.reportMessages(selectAll ? messages : [message], controller)
}))) })))
} else if message.id.peerId.isReplies {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuBlock, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor)
}, action: { controller, f in
interfaceInteraction.blockMessageAuthor(message, controller)
})))
} }
var clearCacheAsDelete = false var clearCacheAsDelete = false

View File

@ -561,7 +561,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} else if incoming { } else if incoming {
hasAvatar = true hasAvatar = true
} }
case let .replyThread(messageId, isChannelPost, _, _): case let .replyThread(messageId, isChannelPost, _, _, _):
if messageId.peerId != item.context.account.peerId { if messageId.peerId != item.context.account.peerId {
if messageId.peerId.isGroupOrChannel && item.message.author != nil { if messageId.peerId.isGroupOrChannel && item.message.author != nil {
var isBroadcastChannel = false var isBroadcastChannel = false
@ -746,7 +746,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { 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 { } else {
replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude))
} }

View File

@ -841,7 +841,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
switch item.chatLocation { switch item.chatLocation {
case let .peer(peerId): case let .peer(peerId):
chatLocationPeerId = peerId chatLocationPeerId = peerId
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
chatLocationPeerId = messageId.peerId chatLocationPeerId = messageId.peerId
} }
@ -885,7 +885,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
allowFullWidth = true allowFullWidth = true
} }
if case let .replyThread(messageId, isChannelPost, _, _) = item.chatLocation, isChannelPost, messageId == firstMessage.id { if case let .replyThread(messageId, isChannelPost, _, _, _) = item.chatLocation, isChannelPost, messageId == firstMessage.id {
isBroadcastChannel = true isBroadcastChannel = true
} }
@ -1048,7 +1048,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
inlineBotNameString = attribute.title inlineBotNameString = attribute.title
} }
} else if let attribute = attribute as? ReplyMessageAttribute { } 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 { } else {
replyMessage = firstMessage.associatedMessages[attribute.messageId] replyMessage = firstMessage.associatedMessages[attribute.messageId]
} }

View File

@ -99,12 +99,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
return return
} }
if item.message.id.peerId.isReplies { if item.message.id.peerId.isReplies {
for attribute in item.message.attributes { item.controllerInteraction.openReplyThreadOriginalMessage(item.message)
if let attribute = attribute as? SourceReferenceMessageAttribute {
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
break
}
}
} else { } else {
item.controllerInteraction.openMessageReplies(item.message.id, true) item.controllerInteraction.openMessageReplies(item.message.id, true)
} }
@ -145,8 +140,6 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
} }
if let maxMessageId = attribute.maxMessageId, let maxReadMessageId = attribute.maxReadMessageId { if let maxMessageId = attribute.maxMessageId, let maxReadMessageId = attribute.maxReadMessageId {
hasUnseenReplies = maxMessageId > maxReadMessageId hasUnseenReplies = maxMessageId > maxReadMessageId
} else if attribute.maxMessageId != nil {
hasUnseenReplies = true
} }
} }
} }

View File

@ -182,7 +182,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
switch item.chatLocation { switch item.chatLocation {
case let .peer(peerId): case let .peer(peerId):
messagePeerId = peerId messagePeerId = peerId
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
messagePeerId = messageId.peerId messagePeerId = messageId.peerId
} }
@ -194,7 +194,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
isBroadcastChannel = true isBroadcastChannel = true
} }
if case let .replyThread(messageId, isChannelPost, _, _) = item.chatLocation, isChannelPost, messageId == item.message.id { if case let .replyThread(messageId, isChannelPost, _, _, _) = item.chatLocation, isChannelPost, messageId == item.message.id {
isBroadcastChannel = true isBroadcastChannel = true
} }
@ -341,7 +341,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { 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 { } else {
replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
} }

View File

@ -280,7 +280,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
switch chatLocation { switch chatLocation {
case let .peer(peerId): case let .peer(peerId):
messagePeerId = peerId messagePeerId = peerId
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
messagePeerId = messageId.peerId messagePeerId = messageId.peerId
} }
@ -333,7 +333,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
isBroadcastChannel = true isBroadcastChannel = true
} }
} else if case let .replyThread(messageId, isChannelPost, _, _) = chatLocation, isChannelPost, messageId == message.id { } else if case let .replyThread(messageId, isChannelPost, _, _, _) = chatLocation, isChannelPost, messageId == message.id {
isBroadcastChannel = true isBroadcastChannel = true
} }
if !hasActionMedia && !isBroadcastChannel { if !hasActionMedia && !isBroadcastChannel {

View File

@ -249,7 +249,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} else if incoming { } else if incoming {
hasAvatar = true hasAvatar = true
} }
case let .replyThread(messageId, isChannelPost, _, _): case let .replyThread(messageId, isChannelPost, _, _, _):
if messageId.peerId != item.context.account.peerId { if messageId.peerId != item.context.account.peerId {
if messageId.peerId.isGroupOrChannel && item.message.author != nil { if messageId.peerId.isGroupOrChannel && item.message.author != nil {
var isBroadcastChannel = false var isBroadcastChannel = false
@ -415,7 +415,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { 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 { } else {
replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
} }
@ -480,6 +480,65 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
layoutSize.height += actionButtonsSizeAndApply.0.height layoutSize.height += actionButtonsSizeAndApply.0.height
} }
var updatedImageFrame = imageFrame.offsetBy(dx: 0.0, dy: floor((contentHeight - imageSize.height) / 2.0))
var dateOffset = CGPoint(x: dateAndStatusSize.width + 4.0, y: dateAndStatusSize.height + 16.0)
if isEmoji {
if incoming {
dateOffset.x = 12.0
} else {
dateOffset.y = 12.0
}
}
var dateAndStatusFrame = CGRect(origin: CGPoint(x: min(layoutSize.width - dateAndStatusSize.width - 14.0, max(displayLeftInset, updatedImageFrame.maxX - dateOffset.x)), y: updatedImageFrame.maxY - dateOffset.y), size: dateAndStatusSize)
let baseShareButtonSize = CGSize(width: 30.0, height: 60.0)
var baseShareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 6.0, y: updatedImageFrame.maxY - 10.0 - baseShareButtonSize.height - 4.0), size: baseShareButtonSize)
if isEmoji && incoming {
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
}
var viaBotFrame: CGRect?
if let (viaBotLayout, _) = viaBotApply {
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size)
}
var replyInfoFrame: CGRect?
if let (replyInfoSize, _) = replyInfoApply {
var viaBotSize = CGSize()
if let viaBotFrame = viaBotFrame {
viaBotSize = viaBotFrame.size
}
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize)
replyInfoFrame = replyInfoFrameValue
if let viaBotFrameValue = viaBotFrame {
if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
viaBotFrame = viaBotFrameValue.offsetBy(dx: replyInfoFrameValue.minX - viaBotFrameValue.minX, dy: 0.0)
}
}
}
var replyBackgroundFrame: CGRect?
if let replyInfoFrame = replyInfoFrame {
var viaBotSize = CGSize()
if let viaBotFrame = viaBotFrame {
viaBotSize = viaBotFrame.size
}
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
}
if let replyBackgroundFrameValue = replyBackgroundFrame {
if replyBackgroundFrameValue.insetBy(dx: -2.0, dy: -2.0).intersects(baseShareButtonFrame) {
let offset: CGFloat = 25.0
layoutSize.height += offset
updatedImageFrame.origin.y += offset
dateAndStatusFrame.origin.y += offset
baseShareButtonFrame.origin.y += offset
}
}
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in
if let strongSelf = self { if let strongSelf = self {
var transition: ContainedViewLayoutTransition = .immediate var transition: ContainedViewLayoutTransition = .immediate
@ -487,7 +546,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
transition = .animated(duration: duration, curve: .spring) transition = .animated(duration: duration, curve: .spring)
} }
let updatedImageFrame = imageFrame.offsetBy(dx: 0.0, dy: floor((contentHeight - imageSize.height) / 2.0))
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame) transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
imageApply() imageApply()
@ -499,15 +557,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
dateAndStatusApply(false) dateAndStatusApply(false)
var dateOffset = CGPoint(x: dateAndStatusSize.width + 4.0, y: dateAndStatusSize.height + 16.0)
if isEmoji {
if incoming {
dateOffset.x = 12.0
} else {
dateOffset.y = 12.0
}
}
let dateAndStatusFrame = CGRect(origin: CGPoint(x: min(layoutSize.width - dateAndStatusSize.width - 14.0, max(displayLeftInset, updatedImageFrame.maxX - dateOffset.x)), y: updatedImageFrame.maxY - dateOffset.y), size: dateAndStatusSize)
transition.updateFrame(node: strongSelf.dateAndStatusNode, frame: dateAndStatusFrame) transition.updateFrame(node: strongSelf.dateAndStatusNode, frame: dateAndStatusFrame)
if let updatedShareButtonNode = updatedShareButtonNode { if let updatedShareButtonNode = updatedShareButtonNode {
@ -520,10 +569,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
} }
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account)
var shareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 6.0, y: updatedImageFrame.maxY - 10.0 - buttonSize.height - 4.0), size: buttonSize) let shareButtonFrame = CGRect(origin: CGPoint(x: baseShareButtonFrame.minX, y: baseShareButtonFrame.maxY - buttonSize.height), size: buttonSize)
if isEmoji && incoming {
shareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
}
transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame) transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame)
} else if let shareButtonNode = strongSelf.shareButtonNode { } else if let shareButtonNode = strongSelf.shareButtonNode {
shareButtonNode.removeFromSupernode() shareButtonNode.removeFromSupernode()
@ -543,13 +589,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = nil strongSelf.replyBackgroundNode = nil
} }
if let (viaBotLayout, viaBotApply) = viaBotApply { if let (_, viaBotApply) = viaBotApply, let viaBotFrame = viaBotFrame {
let viaBotNode = viaBotApply() let viaBotNode = viaBotApply()
if strongSelf.viaBotNode == nil { if strongSelf.viaBotNode == nil {
strongSelf.viaBotNode = viaBotNode strongSelf.viaBotNode = viaBotNode
strongSelf.addSubnode(viaBotNode) strongSelf.addSubnode(viaBotNode)
} }
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size)
viaBotNode.frame = viaBotFrame viaBotNode.frame = viaBotFrame
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 6.0, y: viaBotFrame.minY - 2.0 - UIScreenPixel), size: CGSize(width: viaBotFrame.size.width + 11.0, height: viaBotFrame.size.height + 5.0)) strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 6.0, y: viaBotFrame.minY - 2.0 - UIScreenPixel), size: CGSize(width: viaBotFrame.size.width + 11.0, height: viaBotFrame.size.height + 5.0))
} else if let viaBotNode = strongSelf.viaBotNode { } else if let viaBotNode = strongSelf.viaBotNode {
@ -557,24 +602,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = nil strongSelf.viaBotNode = nil
} }
if let (replyInfoSize, replyInfoApply) = replyInfoApply { if let (_, replyInfoApply) = replyInfoApply, let replyInfoFrame = replyInfoFrame {
let replyInfoNode = replyInfoApply() let replyInfoNode = replyInfoApply()
if strongSelf.replyInfoNode == nil { if strongSelf.replyInfoNode == nil {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.addSubnode(replyInfoNode) strongSelf.addSubnode(replyInfoNode)
} }
var viaBotSize = CGSize()
if let viaBotNode = strongSelf.viaBotNode {
viaBotSize = viaBotNode.frame.size
}
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize)
if let viaBotNode = strongSelf.viaBotNode {
if replyInfoFrame.minX < viaBotNode.frame.minX {
viaBotNode.frame = viaBotNode.frame.offsetBy(dx: replyInfoFrame.minX - viaBotNode.frame.minX, dy: 0.0)
}
}
replyInfoNode.frame = replyInfoFrame replyInfoNode.frame = replyInfoFrame
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame ?? CGRect()
if let _ = item.controllerInteraction.selectionState, isEmoji { if let _ = item.controllerInteraction.selectionState, isEmoji {
replyInfoNode.alpha = 0.0 replyInfoNode.alpha = 0.0

View File

@ -56,6 +56,7 @@ final class ChatPanelInterfaceInteraction {
let deleteSelectedMessages: () -> Void let deleteSelectedMessages: () -> Void
let reportSelectedMessages: () -> Void let reportSelectedMessages: () -> Void
let reportMessages: ([Message], ContextController?) -> Void let reportMessages: ([Message], ContextController?) -> Void
let blockMessageAuthor: (Message, ContextController?) -> Void
let deleteMessages: ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void let deleteMessages: ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void
let forwardSelectedMessages: () -> Void let forwardSelectedMessages: () -> Void
let forwardCurrentForwardMessages: () -> Void let forwardCurrentForwardMessages: () -> Void
@ -130,6 +131,7 @@ final class ChatPanelInterfaceInteraction {
deleteSelectedMessages: @escaping () -> Void, deleteSelectedMessages: @escaping () -> Void,
reportSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void,
reportMessages: @escaping ([Message], ContextController?) -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void,
blockMessageAuthor: @escaping (Message, ContextController?) -> Void,
deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void,
forwardSelectedMessages: @escaping () -> Void, forwardSelectedMessages: @escaping () -> Void,
forwardCurrentForwardMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void,
@ -203,6 +205,7 @@ final class ChatPanelInterfaceInteraction {
self.deleteSelectedMessages = deleteSelectedMessages self.deleteSelectedMessages = deleteSelectedMessages
self.reportSelectedMessages = reportSelectedMessages self.reportSelectedMessages = reportSelectedMessages
self.reportMessages = reportMessages self.reportMessages = reportMessages
self.blockMessageAuthor = blockMessageAuthor
self.deleteMessages = deleteMessages self.deleteMessages = deleteMessages
self.forwardSelectedMessages = forwardSelectedMessages self.forwardSelectedMessages = forwardSelectedMessages
self.forwardCurrentForwardMessages = forwardCurrentForwardMessages self.forwardCurrentForwardMessages = forwardCurrentForwardMessages

View File

@ -58,6 +58,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, deleteSelectedMessages: { }, deleteSelectedMessages: {
}, reportSelectedMessages: { }, reportSelectedMessages: {
}, reportMessages: { _, _ in }, reportMessages: { _, _ in
}, blockMessageAuthor: { _, _ in
}, deleteMessages: { _, _, f in }, deleteMessages: { _, _, f in
f(.default) f(.default)
}, forwardSelectedMessages: { }, forwardSelectedMessages: {

View File

@ -452,6 +452,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
@ -816,9 +817,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(messageId))) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(messageId)))
} }
case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadMessageId, messageId): case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId), subject: .message(messageId)))
} }
case let .stickerPack(name): case let .stickerPack(name):
let packReference: StickerPackReference = .name(name) let packReference: StickerPackReference = .name(name)

View File

@ -792,7 +792,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} }
} else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) {
placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder
} else if case let .replyThread(_, isChannelPost, _, _) = interfaceState.chatLocation { } else if case let .replyThread(_, isChannelPost, _, _, _) = interfaceState.chatLocation {
if isChannelPost { if isChannelPost {
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment
} else { } else {

View File

@ -205,6 +205,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
inputActivitiesAllowed = false inputActivitiesAllowed = false
} }
} }
case .replyThread:
inputActivitiesAllowed = true
default: default:
inputActivitiesAllowed = false inputActivitiesAllowed = false
} }
@ -402,7 +404,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
} }
if self.activityNode.transitionToState(state, animation: .slide) { if self.activityNode.transitionToState(state, animation: .slide) {
self.setNeedsLayout() if let (size, clearBounds) = self.validLayout {
self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring))
}
} }
} }
@ -549,7 +553,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if titleFrame.size.width < size.width { if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
} }
self.titleNode.frame = titleFrame transition.updateFrameAdditiveToCenter(node: self.titleNode, frame: titleFrame)
} else { } else {
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
@ -558,7 +562,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
} }
titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth) titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth)
self.titleNode.frame = titleFrame transition.updateFrameAdditiveToCenter(node: self.titleNode, frame: titleFrame)
var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize) var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
if activitySize.width < size.width { if activitySize.width < size.width {
@ -584,7 +588,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame transition.updateFrameAdditiveToCenter(node: self.titleNode, frame: titleFrame)
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)
if let image = self.titleLeftIconNode.image { if let image = self.titleLeftIconNode.image {

View File

@ -145,6 +145,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -125,7 +125,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
if message.id.peerId == peerId { if message.id.peerId == peerId {
return true return true
} }
case let .replyThread(messageId, _, _, _): case let .replyThread(messageId, _, _, _, _):
if message.id.peerId == messageId.peerId { if message.id.peerId == messageId.peerId {
return true return true
} }

View File

@ -90,9 +90,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
navigationController?.pushViewController(controller) navigationController?.pushViewController(controller)
case let .channelMessage(peerId, messageId): case let .channelMessage(peerId, messageId):
openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)) openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId), peekData: nil))
case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadMessageId, messageId): case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
if let navigationController = navigationController { if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
present(c, a)
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
} }
case let .stickerPack(name): case let .stickerPack(name):
dismissInput() dismissInput()

View File

@ -134,6 +134,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))

View File

@ -361,6 +361,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, reportSelectedMessages: { }, reportSelectedMessages: {
reportMessages() reportMessages()
}, reportMessages: { _, _ in }, reportMessages: { _, _ in
}, blockMessageAuthor: { _, _ in
}, deleteMessages: { _, _, f in }, deleteMessages: { _, _, f in
f(.default) f(.default)
}, forwardSelectedMessages: { }, forwardSelectedMessages: {
@ -1957,6 +1958,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -1199,6 +1199,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil return nil
}, openPeerContextMenu: { _, _, _, _ in }, openPeerContextMenu: { _, _, _, _ in
}, openMessageReplies: { _, _ in }, openMessageReplies: { _, _ in
}, openReplyThreadOriginalMessage: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -59,9 +59,11 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
if let navigationController = controller.navigationController as? NavigationController { if let navigationController = controller.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(messageId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(messageId)))
} }
case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadMessageId, messageId): case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
if let navigationController = controller.navigationController as? NavigationController { if let navigationController = controller.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxMessage: maxMessage, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
} }
case let .stickerPack(name): case let .stickerPack(name):
let packReference: StickerPackReference = .name(name) let packReference: StickerPackReference = .name(name)

View File

@ -160,7 +160,9 @@ static void setBoolField(CustomBlurEffect *object, NSString *name, BOOL value) {
} }
UIBlurEffect *makeCustomZoomBlurEffectImpl() { UIBlurEffect *makeCustomZoomBlurEffectImpl() {
if (@available(iOS 11.0, *)) { if (@available(iOS 13.0, *)) {
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialLight];
} else if (@available(iOS 11.0, *)) {
NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""]; NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""];
CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0]; CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0];

View File

@ -25,7 +25,7 @@ public enum ParsedInternalPeerUrlParameter {
public enum ParsedInternalUrl { public enum ParsedInternalUrl {
case peerName(String, ParsedInternalPeerUrlParameter?) case peerName(String, ParsedInternalPeerUrlParameter?)
case peerId(PeerId) case peerId(PeerId)
case privateMessage(MessageId) case privateMessage(messageId: MessageId, threadId: Int32?)
case stickerPack(String) case stickerPack(String)
case join(String) case join(String)
case localization(String) case localization(String)
@ -240,16 +240,35 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
return .theme(pathComponents[1]) return .theme(pathComponents[1])
} else if pathComponents.count == 3 && pathComponents[0] == "c" { } else if pathComponents.count == 3 && pathComponents[0] == "c" {
if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) { if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
return .privateMessage(MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: messageId)) var threadId: Int32?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "thread" {
if let intValue = Int32(value) {
threadId = intValue
break
}
}
}
}
}
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId)
} else { } else {
return nil return nil
} }
} else if let value = Int(pathComponents[1]) { } else if let value = Int(pathComponents[1]) {
var threadId: Int32?
var commentId: Int32? var commentId: Int32?
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value { if let value = queryItem.value {
if queryItem.name == "comment" { if queryItem.name == "thread" {
if let intValue = Int32(value) {
threadId = intValue
break
}
} else if queryItem.name == "comment" {
if let intValue = Int32(value) { if let intValue = Int32(value) {
commentId = intValue commentId = intValue
break break
@ -258,7 +277,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} }
} }
} }
if let commentId = commentId { if let threadId = threadId {
return .peerName(peerName, .replyThread(threadId, Int32(value)))
} else if let commentId = commentId {
return .peerName(peerName, .replyThread(Int32(value), commentId)) return .peerName(peerName, .replyThread(Int32(value), commentId))
} else { } else {
return .peerName(peerName, .channelMessage(Int32(value))) return .peerName(peerName, .channelMessage(Int32(value)))
@ -305,7 +326,7 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
guard let result = result else { guard let result = result else {
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId) return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId)
} }
return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxMessage: result.maxMessage, maxReadMessageId: result.maxReadMessageId, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxMessage: result.maxMessage, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.maxReadIncomingMessageId, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
} }
} }
} else { } else {
@ -330,21 +351,34 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
return .single(.inaccessiblePeer) return .single(.inaccessiblePeer)
} }
} }
case let .privateMessage(messageId): case let .privateMessage(messageId, threadId):
return account.postbox.transaction { transaction -> Peer? in return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(messageId.peerId) return transaction.getPeer(messageId.peerId)
} }
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in |> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
let foundPeer: Signal<Peer?, NoError>
if let peer = peer { if let peer = peer {
return .single(.peer(peer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil))) foundPeer = .single(peer)
} else { } else {
return findChannelById(postbox: account.postbox, network: account.network, channelId: messageId.peerId.id) foundPeer = findChannelById(postbox: account.postbox, network: account.network, channelId: messageId.peerId.id)
|> map { foundPeer -> ResolvedUrl? in
if let foundPeer = foundPeer {
return .peer(foundPeer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil))
} else {
return .inaccessiblePeer
} }
return foundPeer
|> mapToSignal { foundPeer -> Signal<ResolvedUrl?, NoError> in
if let foundPeer = foundPeer {
if let threadId = threadId {
let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId)
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId)
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId)
}
return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxMessage: result.maxMessage, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.maxReadIncomingMessageId, messageId: messageId)
}
} else {
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)))
}
} else {
return .single(.inaccessiblePeer)
} }
} }
} }

View File

@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
public var Wallet_Send_ConfirmationConfirm: String { return self._s[218]! } public var Wallet_Send_ConfirmationConfirm: String { return self._s[218]! }
public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! } public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! }
public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! } public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! }
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value) let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
} }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value) let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)