mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
95a6190111
@ -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, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, messageId: MessageId)
|
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, 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, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?)
|
case replyThread(ChatReplyThreadMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class NavigateToChatControllerParams {
|
public final class NavigateToChatControllerParams {
|
||||||
|
@ -230,7 +230,7 @@ static int32_t fixedTimeDifferenceValue = 0;
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
_tempKeyExpiration = 1 * 60 * 60;
|
_tempKeyExpiration = 1 * 60 * 60;
|
||||||
#else
|
#else
|
||||||
_tempKeyExpiration = 1 * 60 * 60;
|
_tempKeyExpiration = 24 * 60 * 60;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init];
|
_datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init];
|
||||||
|
@ -6,7 +6,7 @@ public enum ChatLocationInput {
|
|||||||
case external(PeerId, Signal<MessageHistoryViewExternalInput, NoError>)
|
case external(PeerId, Signal<MessageHistoryViewExternalInput, NoError>)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolvedChatLocationInput {
|
public enum ResolvedChatLocationInput {
|
||||||
case peer(PeerId)
|
case peer(PeerId)
|
||||||
case external(MessageHistoryViewExternalInput)
|
case external(MessageHistoryViewExternalInput)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ final class MessageHistoryThreadHoleIndexTable: Table {
|
|||||||
let key = ValueBoxKey(length: 8 + 8 + 4 + 4)
|
let key = ValueBoxKey(length: 8 + 8 + 4 + 4)
|
||||||
key.setInt64(0, value: peerId.toInt64())
|
key.setInt64(0, value: peerId.toInt64())
|
||||||
key.setInt64(8, value: threadId)
|
key.setInt64(8, value: threadId)
|
||||||
key.setInt32(8 + 4, value: namespace)
|
key.setInt32(8 + 8, value: namespace)
|
||||||
let tagValue: UInt32
|
let tagValue: UInt32
|
||||||
switch space {
|
switch space {
|
||||||
case .everywhere:
|
case .everywhere:
|
||||||
|
@ -68,6 +68,11 @@ public final class Transaction {
|
|||||||
return self.postbox?.messageHistoryHoleIndexTable.containing(id: id) ?? [:]
|
return self.postbox?.messageHistoryHoleIndexTable.containing(id: id) ?? [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getHoles(peerId: PeerId, namespace: MessageId.Namespace) -> IndexSet {
|
||||||
|
assert(!self.disposed)
|
||||||
|
return self.postbox?.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1)) ?? IndexSet()
|
||||||
|
}
|
||||||
|
|
||||||
public func addThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange<MessageId.Id>) {
|
public func addThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange<MessageId.Id>) {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
self.postbox?.addThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range)
|
self.postbox?.addThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range)
|
||||||
@ -985,6 +990,26 @@ public final class Transaction {
|
|||||||
self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f)
|
self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getMessagesHistoryViewState(input: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, namespaces: MessageIdNamespaces) -> MessageHistoryView {
|
||||||
|
precondition(!self.disposed)
|
||||||
|
guard let postbox = self.postbox else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
precondition(postbox.queue.isCurrent())
|
||||||
|
|
||||||
|
var view: MessageHistoryView?
|
||||||
|
|
||||||
|
let subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = Subscriber(next: { next in
|
||||||
|
view = next.0
|
||||||
|
}, error: { _ in }, completed: {})
|
||||||
|
|
||||||
|
let disposable = postbox.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: input, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: nil, namespaces: namespaces, orderStatistics: MessageHistoryViewOrderStatistics(), additionalData: [])
|
||||||
|
disposable.dispose()
|
||||||
|
|
||||||
|
return view!
|
||||||
|
}
|
||||||
|
|
||||||
public func invalidateMessageHistoryTagsSummary(peerId: PeerId, namespace: MessageId.Namespace, tagMask: MessageTags) {
|
public func invalidateMessageHistoryTagsSummary(peerId: PeerId, namespace: MessageId.Namespace, tagMask: MessageTags) {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
self.postbox?.invalidateMessageHistoryTagsSummary(peerId: peerId, namespace: namespace, tagMask: tagMask)
|
self.postbox?.invalidateMessageHistoryTagsSummary(peerId: peerId, namespace: namespace, tagMask: tagMask)
|
||||||
@ -2530,7 +2555,7 @@ public final class Postbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
fileprivate func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||||
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
||||||
var mainPeerIdForTopTaggedMessages: PeerId?
|
var mainPeerIdForTopTaggedMessages: PeerId?
|
||||||
switch peerIds {
|
switch peerIds {
|
||||||
|
@ -38,6 +38,7 @@ public func openUserGeneratedUrl(context: AccountContext, url: String, concealed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
|
progressDisposable.dispose()
|
||||||
openResolved(result)
|
openResolved(result)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -2291,21 +2291,21 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
|
|
||||||
final class MessageThreadStatsRecord {
|
final class MessageThreadStatsRecord {
|
||||||
var count: Int = 0
|
var count: Int = 0
|
||||||
var peers: [PeerId] = []
|
var peers: [ReplyThreadUserMessage] = []
|
||||||
}
|
}
|
||||||
var messageThreadStatsDifferences: [MessageId: MessageThreadStatsRecord] = [:]
|
var messageThreadStatsDifferences: [MessageId: MessageThreadStatsRecord] = [:]
|
||||||
func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int, addedMessagePeer: PeerId?) {
|
func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int, addedMessagePeer: PeerId?, addedMessageId: MessageId?, isOutgoing: Bool) {
|
||||||
if let value = messageThreadStatsDifferences[threadMessageId] {
|
if let value = messageThreadStatsDifferences[threadMessageId] {
|
||||||
value.count += add - remove
|
value.count += add - remove
|
||||||
if let addedMessagePeer = addedMessagePeer {
|
if let addedMessagePeer = addedMessagePeer, let addedMessageId = addedMessageId {
|
||||||
value.peers.append(addedMessagePeer)
|
value.peers.append(ReplyThreadUserMessage(id: addedMessagePeer, messageId: addedMessageId, isOutgoing: isOutgoing))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let value = MessageThreadStatsRecord()
|
let value = MessageThreadStatsRecord()
|
||||||
messageThreadStatsDifferences[threadMessageId] = value
|
messageThreadStatsDifferences[threadMessageId] = value
|
||||||
value.count = add - remove
|
value.count = add - remove
|
||||||
if let addedMessagePeer = addedMessagePeer {
|
if let addedMessagePeer = addedMessagePeer, let addedMessageId = addedMessageId {
|
||||||
value.peers.append(addedMessagePeer)
|
value.peers.append(ReplyThreadUserMessage(id: addedMessagePeer, messageId: addedMessageId, isOutgoing: isOutgoing))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2320,7 +2320,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId)
|
let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId)
|
||||||
if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
if !transaction.messageExists(id: id) {
|
if !transaction.messageExists(id: id) {
|
||||||
addMessageThreadStatsDifference(threadMessageId: messageThreadId, add: 1, remove: 0, addedMessagePeer: message.authorId)
|
addMessageThreadStatsDifference(threadMessageId: messageThreadId, add: 1, remove: 0, addedMessagePeer: message.authorId, addedMessageId: id, isOutgoing: !message.flags.contains(.Incoming))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2338,6 +2338,13 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
} else {
|
} else {
|
||||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
|
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
|
||||||
}
|
}
|
||||||
|
if let threadId = message.threadId {
|
||||||
|
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)] == nil {
|
||||||
|
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)] = [authorId: activityValue]
|
||||||
|
} else {
|
||||||
|
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)]![authorId] = activityValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .Id(id) = message.id {
|
if case let .Id(id) = message.id {
|
||||||
@ -2435,7 +2442,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
}
|
}
|
||||||
case let .DeleteMessages(ids):
|
case let .DeleteMessages(ids):
|
||||||
deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in
|
deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in
|
||||||
addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil)
|
addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil, addedMessageId: nil, isOutgoing: false)
|
||||||
})
|
})
|
||||||
case let .UpdateMinAvailableMessage(id):
|
case let .UpdateMinAvailableMessage(id):
|
||||||
if let message = transaction.getMessage(id) {
|
if let message = transaction.getMessage(id) {
|
||||||
|
@ -241,8 +241,15 @@ private final class FeaturedStickerPacksContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct ViewCountContextState {
|
private struct ViewCountContextState {
|
||||||
|
struct ReplyInfo {
|
||||||
|
var commentsPeerId: PeerId?
|
||||||
|
var maxReadIncomingMessageId: MessageId?
|
||||||
|
var maxMessageId: MessageId?
|
||||||
|
}
|
||||||
|
|
||||||
var timestamp: Int32
|
var timestamp: Int32
|
||||||
var clientId: Int32
|
var clientId: Int32
|
||||||
|
var result: ReplyInfo?
|
||||||
|
|
||||||
func isStillValidFor(_ other: ViewCountContextState) -> Bool {
|
func isStillValidFor(_ other: ViewCountContextState) -> Bool {
|
||||||
if other.timestamp > self.timestamp + 30 {
|
if other.timestamp > self.timestamp + 30 {
|
||||||
@ -592,10 +599,42 @@ public final class AccountViewTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct UpdatedMessageReplyInfo {
|
||||||
|
var timestamp: Int32
|
||||||
|
var commentsPeerId: PeerId
|
||||||
|
var maxReadIncomingMessageId: MessageId?
|
||||||
|
var maxMessageId: MessageId?
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMaxReadIncomingMessageIdForReplyInfo(id: MessageId, maxReadIncomingMessageId: MessageId) {
|
||||||
|
self.queue.async {
|
||||||
|
if var state = self.updatedViewCountMessageIdsAndTimestamps[id], var result = state.result {
|
||||||
|
result.maxReadIncomingMessageId = maxReadIncomingMessageId
|
||||||
|
state.result = result
|
||||||
|
self.updatedViewCountMessageIdsAndTimestamps[id] = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func replyInfoForMessageId(_ id: MessageId) -> Signal<UpdatedMessageReplyInfo?, NoError> {
|
||||||
|
return Signal { [weak self] subscriber in
|
||||||
|
let state = self?.updatedViewCountMessageIdsAndTimestamps[id]
|
||||||
|
let result = state?.result
|
||||||
|
if let state = state, let result = result, let commentsPeerId = result.commentsPeerId {
|
||||||
|
subscriber.putNext(UpdatedMessageReplyInfo(timestamp: state.timestamp, commentsPeerId: commentsPeerId, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxMessageId: result.maxMessageId))
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|> runOn(self.queue)
|
||||||
|
}
|
||||||
|
|
||||||
public func updateViewCountForMessageIds(messageIds: Set<MessageId>, clientId: Int32) {
|
public func updateViewCountForMessageIds(messageIds: Set<MessageId>, clientId: Int32) {
|
||||||
self.queue.async {
|
self.queue.async {
|
||||||
var addedMessageIds: [MessageId] = []
|
var addedMessageIds: [MessageId] = []
|
||||||
let updatedState = ViewCountContextState(timestamp: Int32(CFAbsoluteTimeGetCurrent()), clientId: clientId)
|
let updatedState = ViewCountContextState(timestamp: Int32(CFAbsoluteTimeGetCurrent()), clientId: clientId, result: nil)
|
||||||
for messageId in messageIds {
|
for messageId in messageIds {
|
||||||
let messageTimestamp = self.updatedViewCountMessageIdsAndTimestamps[messageId]
|
let messageTimestamp = self.updatedViewCountMessageIdsAndTimestamps[messageId]
|
||||||
if messageTimestamp == nil || !messageTimestamp!.isStillValidFor(updatedState) {
|
if messageTimestamp == nil || !messageTimestamp!.isStillValidFor(updatedState) {
|
||||||
@ -609,106 +648,128 @@ public final class AccountViewTracker {
|
|||||||
self.nextUpdatedViewCountDisposableId += 1
|
self.nextUpdatedViewCountDisposableId += 1
|
||||||
|
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
let signal = (account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
let signal: Signal<[MessageId: ViewCountContextState], NoError> = (account.postbox.transaction { transaction -> Signal<[MessageId: ViewCountContextState], NoError> in
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
|
||||||
return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue))
|
|
||||||
|> map(Optional.init)
|
|
||||||
|> `catch` { _ -> Signal<Api.messages.MessageViews?, NoError> in
|
|
||||||
return .single(nil)
|
|
||||||
}
|
|
||||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
|
||||||
guard case let .messageViews(viewCounts, chats, users)? = result else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
|
|
||||||
return account.postbox.transaction { transaction -> Void in
|
|
||||||
var peers: [Peer] = []
|
|
||||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
|
||||||
|
|
||||||
for apiUser in users {
|
|
||||||
if let user = TelegramUser.merge(transaction.getPeer(apiUser.peerId) as? TelegramUser, rhs: apiUser) {
|
|
||||||
peers.append(user)
|
|
||||||
if let presence = TelegramUserPresence(apiUser: apiUser) {
|
|
||||||
peerPresences[user.id] = presence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for chat in chats {
|
|
||||||
if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) {
|
|
||||||
peers.append(groupOrChannel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
|
|
||||||
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
|
||||||
|
|
||||||
for i in 0 ..< messageIds.count {
|
|
||||||
if i < viewCounts.count {
|
|
||||||
if case let .messageViews(_, views, forwards, replies) = viewCounts[i] {
|
|
||||||
transaction.updateMessage(messageIds[i], update: { currentMessage in
|
|
||||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
|
||||||
var attributes = currentMessage.attributes
|
|
||||||
var foundReplies = false
|
|
||||||
var commentsChannelId: PeerId?
|
|
||||||
var recentRepliersPeerIds: [PeerId]?
|
|
||||||
var repliesCount: Int32?
|
|
||||||
var repliesMaxId: Int32?
|
|
||||||
var repliesReadMaxId: Int32?
|
|
||||||
if let replies = replies {
|
|
||||||
switch replies {
|
|
||||||
case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, maxId, readMaxId):
|
|
||||||
if let channelId = channelId {
|
|
||||||
commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
|
||||||
}
|
|
||||||
repliesCount = repliesCountValue
|
|
||||||
if let recentRepliers = recentRepliers {
|
|
||||||
recentRepliersPeerIds = recentRepliers.map { $0.peerId }
|
|
||||||
} else {
|
|
||||||
recentRepliersPeerIds = nil
|
|
||||||
}
|
|
||||||
repliesMaxId = maxId
|
|
||||||
repliesReadMaxId = readMaxId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop: for j in 0 ..< attributes.count {
|
|
||||||
if let attribute = attributes[j] as? ViewCountMessageAttribute {
|
|
||||||
if let views = views {
|
|
||||||
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
|
|
||||||
}
|
|
||||||
} else if let _ = attributes[j] as? ForwardCountMessageAttribute {
|
|
||||||
if let forwards = forwards {
|
|
||||||
attributes[j] = ForwardCountMessageAttribute(count: Int(forwards))
|
|
||||||
}
|
|
||||||
} else if let _ = attributes[j] as? ReplyThreadMessageAttribute {
|
|
||||||
foundReplies = true
|
|
||||||
if let repliesCount = repliesCount {
|
|
||||||
attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundReplies, let repliesCount = repliesCount {
|
|
||||||
attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId))
|
|
||||||
}
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
} |> switchToLatest)
|
return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.messages.MessageViews?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<[MessageId: ViewCountContextState], NoError> in
|
||||||
|
guard case let .messageViews(viewCounts, chats, users)? = result else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.postbox.transaction { transaction -> [MessageId: ViewCountContextState] in
|
||||||
|
var peers: [Peer] = []
|
||||||
|
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||||
|
|
||||||
|
var resultStates: [MessageId: ViewCountContextState] = [:]
|
||||||
|
|
||||||
|
for apiUser in users {
|
||||||
|
if let user = TelegramUser.merge(transaction.getPeer(apiUser.peerId) as? TelegramUser, rhs: apiUser) {
|
||||||
|
peers.append(user)
|
||||||
|
if let presence = TelegramUserPresence(apiUser: apiUser) {
|
||||||
|
peerPresences[user.id] = presence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for chat in chats {
|
||||||
|
if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) {
|
||||||
|
peers.append(groupOrChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
|
||||||
|
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||||
|
|
||||||
|
for i in 0 ..< messageIds.count {
|
||||||
|
if i < viewCounts.count {
|
||||||
|
if case let .messageViews(_, views, forwards, replies) = viewCounts[i] {
|
||||||
|
transaction.updateMessage(messageIds[i], update: { currentMessage in
|
||||||
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
var foundReplies = false
|
||||||
|
var commentsChannelId: PeerId?
|
||||||
|
var recentRepliersPeerIds: [PeerId]?
|
||||||
|
var repliesCount: Int32?
|
||||||
|
var repliesMaxId: Int32?
|
||||||
|
var repliesReadMaxId: Int32?
|
||||||
|
if let replies = replies {
|
||||||
|
switch replies {
|
||||||
|
case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, maxId, readMaxId):
|
||||||
|
if let channelId = channelId {
|
||||||
|
commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||||
|
}
|
||||||
|
repliesCount = repliesCountValue
|
||||||
|
if let recentRepliers = recentRepliers {
|
||||||
|
recentRepliersPeerIds = recentRepliers.map { $0.peerId }
|
||||||
|
} else {
|
||||||
|
recentRepliersPeerIds = nil
|
||||||
|
}
|
||||||
|
repliesMaxId = maxId
|
||||||
|
repliesReadMaxId = readMaxId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var maxReadIncomingMessageId: MessageId?
|
||||||
|
var maxMessageId: MessageId?
|
||||||
|
if let commentsChannelId = commentsChannelId {
|
||||||
|
if let repliesReadMaxId = repliesReadMaxId {
|
||||||
|
maxReadIncomingMessageId = MessageId(peerId: commentsChannelId, namespace: Namespaces.Message.Cloud, id: repliesReadMaxId)
|
||||||
|
}
|
||||||
|
if let repliesMaxId = repliesMaxId {
|
||||||
|
maxMessageId = MessageId(peerId: commentsChannelId, namespace: Namespaces.Message.Cloud, id: repliesMaxId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultStates[messageIds[i]] = ViewCountContextState(timestamp: Int32(CFAbsoluteTimeGetCurrent()), clientId: clientId, result: ViewCountContextState.ReplyInfo(commentsPeerId: commentsChannelId, maxReadIncomingMessageId: maxReadIncomingMessageId, maxMessageId: maxMessageId))
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ViewCountMessageAttribute {
|
||||||
|
if let views = views {
|
||||||
|
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
|
||||||
|
}
|
||||||
|
} else if let _ = attributes[j] as? ForwardCountMessageAttribute {
|
||||||
|
if let forwards = forwards {
|
||||||
|
attributes[j] = ForwardCountMessageAttribute(count: Int(forwards))
|
||||||
|
}
|
||||||
|
} else if let _ = attributes[j] as? ReplyThreadMessageAttribute {
|
||||||
|
foundReplies = true
|
||||||
|
if let repliesCount = repliesCount {
|
||||||
|
attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundReplies, let repliesCount = repliesCount {
|
||||||
|
attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId))
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultStates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> switchToLatest)
|
||||||
|> afterDisposed { [weak self] in
|
|> afterDisposed { [weak self] in
|
||||||
self?.queue.async {
|
self?.queue.async {
|
||||||
self?.updatedViewCountDisposables.set(nil, forKey: disposableId)
|
self?.updatedViewCountDisposables.set(nil, forKey: disposableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.updatedViewCountDisposables.set(signal.start(), forKey: disposableId)
|
|> deliverOn(self.queue)
|
||||||
|
self.updatedViewCountDisposables.set(signal.start(next: { [weak self] updatedStates in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (id, state) in updatedStates {
|
||||||
|
strongSelf.updatedViewCountMessageIdsAndTimestamps[id] = state
|
||||||
|
}
|
||||||
|
}), forKey: disposableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1116,7 +1177,7 @@ public final class AccountViewTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func polledChannel(peerId: PeerId) -> Signal<Void, NoError> {
|
public func polledChannel(peerId: PeerId) -> Signal<Void, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.queue.async {
|
self.queue.async {
|
||||||
|
@ -55,7 +55,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
|||||||
case let .chatForbidden(id, title):
|
case let .chatForbidden(id, title):
|
||||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||||
case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, defaultBannedRights, _):
|
case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, defaultBannedRights, _):
|
||||||
let isMin = (flags & (1 << 20)) != 0
|
let isMin = (flags & (1 << 12)) != 0
|
||||||
|
|
||||||
let participationStatus: TelegramChannelParticipationStatus
|
let participationStatus: TelegramChannelParticipationStatus
|
||||||
if (flags & Int32(1 << 1)) != 0 {
|
if (flags & Int32(1 << 1)) != 0 {
|
||||||
@ -130,7 +130,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
||||||
return parseTelegramGroupOrChannel(chat: rhs)
|
return parseTelegramGroupOrChannel(chat: rhs)
|
||||||
case let .channel(flags, _, accessHash, title, username, photo, _, _, _, _, _, defaultBannedRights, _/*feed*//*, feedId*/):
|
case let .channel(flags, _, accessHash, title, username, photo, _, _, _, _, _, defaultBannedRights, _):
|
||||||
let isMin = (flags & (1 << 12)) != 0
|
let isMin = (flags & (1 << 12)) != 0
|
||||||
if accessHash != nil && !isMin {
|
if accessHash != nil && !isMin {
|
||||||
return parseTelegramGroupOrChannel(chat: rhs)
|
return parseTelegramGroupOrChannel(chat: rhs)
|
||||||
@ -149,9 +149,45 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
|||||||
let infoFlags = TelegramChannelGroupFlags()
|
let infoFlags = TelegramChannelGroupFlags()
|
||||||
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||||
}
|
}
|
||||||
|
|
||||||
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
|
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChannel {
|
||||||
|
guard let lhs = lhs else {
|
||||||
|
return rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .personal = rhs.accessHash {
|
||||||
|
return rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelFlags = lhs.flags
|
||||||
|
if rhs.flags.contains(.isVerified) {
|
||||||
|
channelFlags.insert(.isVerified)
|
||||||
|
} else {
|
||||||
|
let _ = channelFlags.remove(.isVerified)
|
||||||
|
}
|
||||||
|
var info = lhs.info
|
||||||
|
switch info {
|
||||||
|
case .broadcast:
|
||||||
|
break
|
||||||
|
case .group:
|
||||||
|
let infoFlags = TelegramChannelGroupFlags()
|
||||||
|
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||||
|
}
|
||||||
|
|
||||||
|
let accessHash: TelegramPeerAccessHash?
|
||||||
|
if let rhsAccessHashValue = lhs.accessHash, case .personal = rhsAccessHashValue {
|
||||||
|
accessHash = rhsAccessHashValue
|
||||||
|
} else {
|
||||||
|
accessHash = lhs.accessHash ?? rhs.accessHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||||||
if let threadId = updatedMessage.threadId {
|
if let threadId = updatedMessage.threadId {
|
||||||
let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId)
|
let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId)
|
||||||
if let authorId = updatedMessage.authorId {
|
if let authorId = updatedMessage.authorId {
|
||||||
updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [authorId])
|
updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [ReplyThreadUserMessage(id: authorId, messageId: updatedId, isOutgoing: true)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ private final class HistoryPreloadEntry: Comparable {
|
|||||||
|> mapToSignal { download -> Signal<Never, NoError> in
|
|> mapToSignal { download -> Signal<Never, NoError> in
|
||||||
switch hole.hole {
|
switch hole.hole {
|
||||||
case let .peer(peerHole):
|
case let .peer(peerHole):
|
||||||
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, threadId: nil, count: 60)
|
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerInput: .direct(peerId: peerHole.peerId, threadId: nil), namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60)
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,39 @@ func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHis
|
|||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal<IndexSet, NoError> {
|
enum FetchMessageHistoryHoleThreadInput: CustomStringConvertible {
|
||||||
|
case direct(peerId: PeerId, threadId: Int64?)
|
||||||
|
case threadFromChannel(channelMessageId: MessageId)
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case let .direct(peerId, threadId):
|
||||||
|
return "direct(\(peerId), \(String(describing: threadId))"
|
||||||
|
case let .threadFromChannel(channelMessageId):
|
||||||
|
return "threadFromChannel(peerId: \(channelMessageId.peerId), postId: \(channelMessageId.id)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestThreadId: MessageId? {
|
||||||
|
switch self {
|
||||||
|
case let .direct(peerId, threadId):
|
||||||
|
if let threadId = threadId {
|
||||||
|
return makeThreadIdMessageId(peerId: peerId, threadId: threadId)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case let .threadFromChannel(channelMessageId):
|
||||||
|
return channelMessageId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FetchMessageHistoryHoleResult: Equatable {
|
||||||
|
var removedIndices: IndexSet
|
||||||
|
var strictRemovedIndices: IndexSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal<FetchMessageHistoryHoleResult, NoError> {
|
||||||
let count = min(100, rawCount)
|
let count = min(100, rawCount)
|
||||||
|
|
||||||
return postbox.stateView()
|
return postbox.stateView()
|
||||||
@ -137,389 +169,377 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { _ -> Signal<IndexSet, NoError> in
|
|> mapToSignal { _ -> Signal<FetchMessageHistoryHoleResult, NoError> in
|
||||||
return postbox.transaction { transaction -> (Peer?, Int32) in
|
return postbox.transaction { transaction -> (Api.InputPeer?, Int32) in
|
||||||
var hash: Int32 = 0
|
switch peerInput {
|
||||||
switch space {
|
case let .direct(peerId, _):
|
||||||
case .everywhere:
|
return (transaction.getPeer(peerId).flatMap(forceApiInputPeer), 0)
|
||||||
if let threadId = threadId {
|
case let .threadFromChannel(channelMessageId):
|
||||||
let offsetId: Int32
|
return (transaction.getPeer(channelMessageId.peerId).flatMap(forceApiInputPeer), 0)
|
||||||
let addOffset: Int32
|
}
|
||||||
let selectedLimit = count
|
}
|
||||||
let maxId: Int32
|
|> mapToSignal { (inputPeer, hash) -> Signal<FetchMessageHistoryHoleResult, NoError> in
|
||||||
let minId: Int32
|
guard let inputPeer = inputPeer else {
|
||||||
|
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet()))
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
|
||||||
addOffset = Int32(-selectedLimit)
|
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
} else {
|
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
addOffset = 0
|
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
}
|
|
||||||
case let .aroundId(id):
|
|
||||||
offsetId = id.id
|
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
|
||||||
maxId = Int32.max
|
|
||||||
minId = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
//request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (transaction.getPeer(peerId), hash)
|
print("fetchMessageHistoryHole for \(peerInput) direction \(direction) space \(space)")
|
||||||
}
|
Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peerInput) direction \(direction) space \(space)")
|
||||||
|> mapToSignal { (peer, hash) -> Signal<IndexSet, NoError> in
|
let request: Signal<Api.messages.Messages, MTRpcError>
|
||||||
guard let peer = peer else {
|
var implicitelyFillHole = false
|
||||||
return .single(IndexSet())
|
let minMaxRange: ClosedRange<MessageId.Id>
|
||||||
}
|
|
||||||
if let inputPeer = forceApiInputPeer(peer) {
|
switch space {
|
||||||
print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)")
|
case .everywhere:
|
||||||
Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)")
|
if let requestThreadId = peerInput.requestThreadId {
|
||||||
let request: Signal<Api.messages.Messages, MTRpcError>
|
let offsetId: Int32
|
||||||
var implicitelyFillHole = false
|
let addOffset: Int32
|
||||||
let minMaxRange: ClosedRange<MessageId.Id>
|
let selectedLimit = count
|
||||||
|
let maxId: Int32
|
||||||
switch space {
|
let minId: Int32
|
||||||
case .everywhere:
|
|
||||||
if let threadId = threadId {
|
switch direction {
|
||||||
let offsetId: Int32
|
case let .range(start, end):
|
||||||
let addOffset: Int32
|
if start.id <= end.id {
|
||||||
let selectedLimit = count
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
let maxId: Int32
|
addOffset = Int32(-selectedLimit)
|
||||||
let minId: Int32
|
maxId = end.id
|
||||||
|
minId = start.id - 1
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
let rangeStartId = start.id
|
||||||
if start.id <= end.id {
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
if rangeStartId <= rangeEndId {
|
||||||
addOffset = Int32(-selectedLimit)
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
|
|
||||||
let rangeStartId = start.id
|
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
addOffset = 0
|
assertionFailure()
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case let .aroundId(id):
|
} else {
|
||||||
offsetId = id.id
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
addOffset = 0
|
||||||
maxId = Int32.max
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
minId = 1
|
minId = end.id
|
||||||
|
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
let rangeStartId = end.id
|
||||||
}
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: hash))
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
} else {
|
|
||||||
let offsetId: Int32
|
|
||||||
let addOffset: Int32
|
|
||||||
let selectedLimit = count
|
|
||||||
let maxId: Int32
|
|
||||||
let minId: Int32
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
|
||||||
addOffset = Int32(-selectedLimit)
|
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
|
|
||||||
let rangeStartId = start.id
|
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
addOffset = 0
|
assertionFailure()
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case let .aroundId(id):
|
|
||||||
offsetId = id.id
|
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
|
||||||
maxId = Int32.max
|
|
||||||
minId = 1
|
|
||||||
minMaxRange = 1 ... Int32.max - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
|
||||||
}
|
|
||||||
case let .tag(tag):
|
|
||||||
assert(tag.containsSingleElement)
|
|
||||||
if tag == .unseenPersonalMessage {
|
|
||||||
let offsetId: Int32
|
|
||||||
let addOffset: Int32
|
|
||||||
let selectedLimit = count
|
|
||||||
let maxId: Int32
|
|
||||||
let minId: Int32
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
|
||||||
addOffset = Int32(-selectedLimit)
|
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
|
|
||||||
let rangeStartId = start.id
|
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
addOffset = 0
|
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case let .aroundId(id):
|
|
||||||
offsetId = id.id
|
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
|
||||||
maxId = Int32.max
|
|
||||||
minId = 1
|
|
||||||
|
|
||||||
minMaxRange = 1 ... Int32.max - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
|
||||||
} else if tag == .liveLocation {
|
|
||||||
let selectedLimit = count
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case .aroundId, .range:
|
|
||||||
implicitelyFillHole = true
|
|
||||||
}
|
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
|
||||||
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
|
||||||
} else if let filter = messageFilterForTagMask(tag) {
|
|
||||||
let offsetId: Int32
|
|
||||||
let addOffset: Int32
|
|
||||||
let selectedLimit = count
|
|
||||||
let maxId: Int32
|
|
||||||
let minId: Int32
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
|
||||||
addOffset = Int32(-selectedLimit)
|
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
|
|
||||||
let rangeStartId = start.id
|
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
addOffset = 0
|
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case let .aroundId(id):
|
|
||||||
offsetId = id.id
|
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
|
||||||
maxId = Int32.max
|
|
||||||
minId = 1
|
|
||||||
|
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
|
||||||
} else {
|
|
||||||
assertionFailure()
|
|
||||||
minMaxRange = 1 ... 1
|
|
||||||
request = .never()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return request
|
|
||||||
|> retryRequest
|
|
||||||
|> mapToSignal { result -> Signal<IndexSet, NoError> in
|
|
||||||
let messages: [Api.Message]
|
|
||||||
let chats: [Api.Chat]
|
|
||||||
let users: [Api.User]
|
|
||||||
var channelPts: Int32?
|
|
||||||
switch result {
|
|
||||||
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
channelPts = pts
|
|
||||||
case .messagesNotModified:
|
|
||||||
messages = []
|
|
||||||
chats = []
|
|
||||||
users = []
|
|
||||||
}
|
|
||||||
|
|
||||||
var peers: [Peer] = []
|
|
||||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
|
||||||
for chat in chats {
|
|
||||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
|
||||||
peers.append(groupOrChannel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for user in users {
|
|
||||||
let telegramUser = TelegramUser(user: user)
|
|
||||||
peers.append(telegramUser)
|
|
||||||
if let presence = TelegramUserPresence(apiUser: user) {
|
|
||||||
peerPresences[telegramUser.id] = presence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var storeMessages: [StoreMessage] = []
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
if let storeMessage = StoreMessage(apiMessage: message, namespace: namespace) {
|
|
||||||
if let channelPts = channelPts {
|
|
||||||
var attributes = storeMessage.attributes
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
|
||||||
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
|
||||||
} else {
|
|
||||||
storeMessages.append(storeMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> IndexSet in
|
|
||||||
let _ = transaction.addMessages(storeMessages, location: .Random)
|
|
||||||
let _ = transaction.addMessages(additionalMessages, location: .Random)
|
|
||||||
var filledRange: ClosedRange<MessageId.Id>
|
|
||||||
let ids = storeMessages.compactMap { message -> MessageId.Id? in
|
|
||||||
switch message.id {
|
|
||||||
case let .Id(id):
|
|
||||||
switch space {
|
|
||||||
case let .tag(tag):
|
|
||||||
if !message.tags.contains(tag) {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return id.id
|
|
||||||
}
|
|
||||||
case .everywhere:
|
|
||||||
return id.id
|
|
||||||
}
|
}
|
||||||
case .Partial:
|
case let .aroundId(id):
|
||||||
return nil
|
offsetId = id.id
|
||||||
}
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
}
|
maxId = Int32.max
|
||||||
if ids.count == 0 || implicitelyFillHole {
|
minId = 1
|
||||||
filledRange = minMaxRange
|
|
||||||
} else {
|
minMaxRange = 1 ... (Int32.max - 1)
|
||||||
let messageRange = ids.min()! ... ids.max()!
|
|
||||||
switch direction {
|
|
||||||
case let .aroundId(aroundId):
|
|
||||||
filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound)
|
|
||||||
if threadId != nil {
|
|
||||||
if ids.count <= count / 2 - 1 {
|
|
||||||
filledRange = minMaxRange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
let minBound = start.id
|
|
||||||
let maxBound = messageRange.upperBound
|
|
||||||
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
|
|
||||||
} else {
|
|
||||||
let minBound = messageRange.lowerBound
|
|
||||||
let maxBound = start.id
|
|
||||||
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: requestThreadId.id, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: hash))
|
||||||
|
} else {
|
||||||
|
let offsetId: Int32
|
||||||
|
let addOffset: Int32
|
||||||
|
let selectedLimit = count
|
||||||
|
let maxId: Int32
|
||||||
|
let minId: Int32
|
||||||
|
|
||||||
|
switch direction {
|
||||||
|
case let .range(start, end):
|
||||||
|
if start.id <= end.id {
|
||||||
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
|
addOffset = Int32(-selectedLimit)
|
||||||
|
maxId = end.id
|
||||||
|
minId = start.id - 1
|
||||||
|
|
||||||
|
let rangeStartId = start.id
|
||||||
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
addOffset = 0
|
||||||
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
minId = end.id
|
||||||
|
|
||||||
|
let rangeStartId = end.id
|
||||||
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .aroundId(id):
|
||||||
|
offsetId = id.id
|
||||||
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
|
maxId = Int32.max
|
||||||
|
minId = 1
|
||||||
|
minMaxRange = 1 ... Int32.max - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||||
|
}
|
||||||
|
case let .tag(tag):
|
||||||
|
assert(tag.containsSingleElement)
|
||||||
|
if tag == .unseenPersonalMessage {
|
||||||
|
let offsetId: Int32
|
||||||
|
let addOffset: Int32
|
||||||
|
let selectedLimit = count
|
||||||
|
let maxId: Int32
|
||||||
|
let minId: Int32
|
||||||
|
|
||||||
|
switch direction {
|
||||||
|
case let .range(start, end):
|
||||||
|
if start.id <= end.id {
|
||||||
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
|
addOffset = Int32(-selectedLimit)
|
||||||
|
maxId = end.id
|
||||||
|
minId = start.id - 1
|
||||||
|
|
||||||
|
let rangeStartId = start.id
|
||||||
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
addOffset = 0
|
||||||
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
minId = end.id
|
||||||
|
|
||||||
|
let rangeStartId = end.id
|
||||||
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .aroundId(id):
|
||||||
|
offsetId = id.id
|
||||||
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
|
maxId = Int32.max
|
||||||
|
minId = 1
|
||||||
|
|
||||||
|
minMaxRange = 1 ... Int32.max - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
||||||
|
} else if tag == .liveLocation {
|
||||||
|
let selectedLimit = count
|
||||||
|
|
||||||
|
switch direction {
|
||||||
|
case .aroundId, .range:
|
||||||
|
implicitelyFillHole = true
|
||||||
|
}
|
||||||
|
minMaxRange = 1 ... (Int32.max - 1)
|
||||||
|
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
||||||
|
} else if let filter = messageFilterForTagMask(tag) {
|
||||||
|
let offsetId: Int32
|
||||||
|
let addOffset: Int32
|
||||||
|
let selectedLimit = count
|
||||||
|
let maxId: Int32
|
||||||
|
let minId: Int32
|
||||||
|
|
||||||
|
switch direction {
|
||||||
|
case let .range(start, end):
|
||||||
|
if start.id <= end.id {
|
||||||
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
|
addOffset = Int32(-selectedLimit)
|
||||||
|
maxId = end.id
|
||||||
|
minId = start.id - 1
|
||||||
|
|
||||||
|
let rangeStartId = start.id
|
||||||
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
addOffset = 0
|
||||||
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
minId = end.id
|
||||||
|
|
||||||
|
let rangeStartId = end.id
|
||||||
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .aroundId(id):
|
||||||
|
offsetId = id.id
|
||||||
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
|
maxId = Int32.max
|
||||||
|
minId = 1
|
||||||
|
|
||||||
|
minMaxRange = 1 ... (Int32.max - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
minMaxRange = 1 ... 1
|
||||||
|
request = .never()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
|> retryRequest
|
||||||
|
|> mapToSignal { result -> Signal<FetchMessageHistoryHoleResult, NoError> in
|
||||||
|
let messages: [Api.Message]
|
||||||
|
let chats: [Api.Chat]
|
||||||
|
let users: [Api.User]
|
||||||
|
var channelPts: Int32?
|
||||||
|
switch result {
|
||||||
|
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
channelPts = pts
|
||||||
|
case .messagesNotModified:
|
||||||
|
messages = []
|
||||||
|
chats = []
|
||||||
|
users = []
|
||||||
|
}
|
||||||
|
|
||||||
|
var peers: [Peer] = []
|
||||||
|
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||||
|
for chat in chats {
|
||||||
|
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
|
peers.append(groupOrChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for user in users {
|
||||||
|
let telegramUser = TelegramUser(user: user)
|
||||||
|
peers.append(telegramUser)
|
||||||
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var storeMessages: [StoreMessage] = []
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
if let storeMessage = StoreMessage(apiMessage: message, namespace: namespace) {
|
||||||
|
if let channelPts = channelPts {
|
||||||
|
var attributes = storeMessage.attributes
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||||
|
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
||||||
|
} else {
|
||||||
|
storeMessages.append(storeMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult in
|
||||||
|
let _ = transaction.addMessages(storeMessages, location: .Random)
|
||||||
|
let _ = transaction.addMessages(additionalMessages, location: .Random)
|
||||||
|
var filledRange: ClosedRange<MessageId.Id>
|
||||||
|
var strictFilledIndices: IndexSet
|
||||||
|
let ids = storeMessages.compactMap { message -> MessageId.Id? in
|
||||||
|
switch message.id {
|
||||||
|
case let .Id(id):
|
||||||
|
switch space {
|
||||||
|
case let .tag(tag):
|
||||||
|
if !message.tags.contains(tag) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return id.id
|
||||||
|
}
|
||||||
|
case .everywhere:
|
||||||
|
return id.id
|
||||||
|
}
|
||||||
|
case .Partial:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ids.count == 0 || implicitelyFillHole {
|
||||||
|
filledRange = minMaxRange
|
||||||
|
strictFilledIndices = IndexSet()
|
||||||
|
} else {
|
||||||
|
let messageRange = ids.min()! ... ids.max()!
|
||||||
|
switch direction {
|
||||||
|
case let .aroundId(aroundId):
|
||||||
|
filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound)
|
||||||
|
strictFilledIndices = IndexSet(integersIn: Int(min(aroundId.id, messageRange.lowerBound)) ... Int(max(aroundId.id, messageRange.upperBound)))
|
||||||
|
if peerInput.requestThreadId != nil {
|
||||||
|
if ids.count <= count / 2 - 1 {
|
||||||
|
filledRange = minMaxRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .range(start, end):
|
||||||
|
if start.id <= end.id {
|
||||||
|
let minBound = start.id
|
||||||
|
let maxBound = messageRange.upperBound
|
||||||
|
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
|
||||||
|
|
||||||
|
var maxStrictIndex = max(minBound, maxBound)
|
||||||
|
maxStrictIndex = min(maxStrictIndex, messageRange.upperBound)
|
||||||
|
strictFilledIndices = IndexSet(integersIn: Int(min(minBound, maxBound)) ... Int(maxStrictIndex))
|
||||||
|
} else {
|
||||||
|
let minBound = messageRange.lowerBound
|
||||||
|
let maxBound = start.id
|
||||||
|
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
|
||||||
|
|
||||||
|
var maxStrictIndex = max(minBound, maxBound)
|
||||||
|
maxStrictIndex = min(maxStrictIndex, messageRange.upperBound)
|
||||||
|
strictFilledIndices = IndexSet(integersIn: Int(min(minBound, maxBound)) ... Int(maxStrictIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch peerInput {
|
||||||
|
case let .direct(peerId, threadId):
|
||||||
if let threadId = threadId {
|
if let threadId = threadId {
|
||||||
transaction.removeThreadIndexHole(peerId: peerId, threadId: makeMessageThreadId(threadId), namespace: namespace, space: .everywhere, range: filledRange)
|
for range in strictFilledIndices.rangeView {
|
||||||
|
transaction.removeThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: Int32(range.lowerBound) ... Int32(range.upperBound))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange)
|
transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange)
|
||||||
}
|
}
|
||||||
|
case .threadFromChannel:
|
||||||
updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in
|
break
|
||||||
return updated
|
}
|
||||||
})
|
|
||||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in
|
||||||
|
return updated
|
||||||
print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) threadId: \(String(describing: threadId)) done")
|
|
||||||
|
|
||||||
return IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound))
|
|
||||||
})
|
})
|
||||||
}
|
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||||
} else {
|
|
||||||
return .complete()
|
print("fetchMessageHistoryHole for \(peerInput) space \(space) done")
|
||||||
|
|
||||||
|
return FetchMessageHistoryHoleResult(
|
||||||
|
removedIndices: IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)),
|
||||||
|
strictRemovedIndices: strictFilledIndices
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -666,10 +686,11 @@ func fetchCallListHole(network: Network, postbox: Postbox, accountPeerId: PeerId
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||||
peers.append(telegramUser)
|
peers.append(telegramUser)
|
||||||
if let presence = TelegramUserPresence(apiUser: user) {
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
peerPresences[telegramUser.id] = presence
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox
|
|||||||
for (entry, disposable) in added {
|
for (entry, disposable) in added {
|
||||||
switch entry.hole {
|
switch entry.hole {
|
||||||
case let .peer(hole):
|
case let .peer(hole):
|
||||||
disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: nil, count: entry.count).start())
|
disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerInput: .direct(peerId: hole.peerId, threadId: nil), namespace: hole.namespace, direction: entry.direction, space: entry.space, count: entry.count).start())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,14 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
|
private struct DiscussionMessage {
|
||||||
|
public var messageId: MessageId
|
||||||
|
public var isChannelPost: Bool
|
||||||
|
public var maxMessage: MessageId?
|
||||||
|
public var maxReadIncomingMessageId: MessageId?
|
||||||
|
public var maxReadOutgoingMessageId: MessageId?
|
||||||
|
}
|
||||||
|
|
||||||
private class ReplyThreadHistoryContextImpl {
|
private class ReplyThreadHistoryContextImpl {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
@ -41,37 +49,34 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
private var initialStateDisposable: Disposable?
|
private var initialStateDisposable: Disposable?
|
||||||
private var holesDisposable: Disposable?
|
private var holesDisposable: Disposable?
|
||||||
private var readStateDisposable: Disposable?
|
private var readStateDisposable: Disposable?
|
||||||
|
private var updateInitialStateDisposable: Disposable?
|
||||||
private let readDisposable = MetaDisposable()
|
private let readDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(queue: Queue, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
|
init(queue: Queue, account: Account, data: ChatReplyThreadMessage) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
self.messageId = messageId
|
self.messageId = data.messageId
|
||||||
|
|
||||||
self.maxReadOutgoingMessageIdValue = maxReadOutgoingMessageId
|
self.maxReadOutgoingMessageIdValue = data.maxReadOutgoingMessageId
|
||||||
self.maxReadOutgoingMessageId.set(.single(self.maxReadOutgoingMessageIdValue))
|
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: data.messageId.peerId, threadId: makeMessageThreadId(data.messageId), namespace: Namespaces.Message.Cloud)
|
||||||
switch maxMessage {
|
indices.subtract(data.initialFilledHoles)
|
||||||
case .unknown:
|
|
||||||
indices.insert(integersIn: 1 ..< Int(Int32.max - 1))
|
let isParticipant = transaction.getPeerChatListIndex(data.messageId.peerId) != nil
|
||||||
case let .known(maxMessageId):
|
if isParticipant {
|
||||||
indices.insert(integersIn: 1 ..< Int(Int32.max - 1))
|
let historyHoles = transaction.getHoles(peerId: data.messageId.peerId, namespace: Namespaces.Message.Cloud)
|
||||||
/*if let maxMessageId = maxMessageId {
|
indices.formIntersection(historyHoles)
|
||||||
let topMessage = transaction.getMessagesWithThreadId(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud, threadId: makeMessageThreadId(messageId), from: MessageIndex.upperBound(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud), includeFrom: false, to: MessageIndex.lowerBound(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud), limit: 1).first
|
|
||||||
if let topMessage = topMessage {
|
|
||||||
if maxMessageId.id < maxMessageId.id {
|
|
||||||
indices.insert(integersIn: Int(topMessage.id.id + 1) ..< Int(Int32.max - 1))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
indices.insert(integersIn: 1 ..< Int(Int32.max - 1))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
indices = IndexSet()
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
return State(messageId: messageId, holeIndices: [Namespaces.Message.Cloud: indices], maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
|
||||||
|
if let maxMessageId = data.maxMessage {
|
||||||
|
indices.remove(integersIn: Int(maxMessageId.id + 1) ..< Int(Int32.max))
|
||||||
|
} else {
|
||||||
|
indices.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
return State(messageId: data.messageId, holeIndices: [Namespaces.Message.Cloud: indices], maxReadIncomingMessageId: data.maxReadIncomingMessageId, maxReadOutgoingMessageId: data.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 {
|
||||||
@ -108,8 +113,102 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let value = outgoing[messageId] {
|
if let value = outgoing[data.messageId] {
|
||||||
strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud, id: value)
|
strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: data.messageId.peerId, namespace: Namespaces.Message.Cloud, id: value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let updateInitialState: Signal<DiscussionMessage, FetchChannelReplyThreadMessageError> = account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(data.messageId.peerId).flatMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> mapToSignal { inputPeer -> Signal<DiscussionMessage, FetchChannelReplyThreadMessageError> in
|
||||||
|
guard let inputPeer = inputPeer else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: data.messageId.id))
|
||||||
|
|> mapError { _ -> FetchChannelReplyThreadMessageError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { discussionMessage -> Signal<DiscussionMessage, FetchChannelReplyThreadMessageError> in
|
||||||
|
return account.postbox.transaction { transaction -> Signal<DiscussionMessage, FetchChannelReplyThreadMessageError> in
|
||||||
|
switch discussionMessage {
|
||||||
|
case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users):
|
||||||
|
let parsedMessages = messages.compactMap { message -> StoreMessage? in
|
||||||
|
StoreMessage(apiMessage: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let topMessage = parsedMessages.last, let parsedIndex = topMessage.index else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
var peers: [Peer] = []
|
||||||
|
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||||
|
|
||||||
|
for chat in chats {
|
||||||
|
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
|
peers.append(groupOrChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for user in users {
|
||||||
|
let telegramUser = TelegramUser(user: user)
|
||||||
|
peers.append(telegramUser)
|
||||||
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = transaction.addMessages(parsedMessages, location: .Random)
|
||||||
|
|
||||||
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
|
||||||
|
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||||
|
|
||||||
|
let resolvedMaxMessage: MessageId?
|
||||||
|
if let maxId = maxId {
|
||||||
|
resolvedMaxMessage = MessageId(
|
||||||
|
peerId: parsedIndex.id.peerId,
|
||||||
|
namespace: Namespaces.Message.Cloud,
|
||||||
|
id: maxId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resolvedMaxMessage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return .single(DiscussionMessage(
|
||||||
|
messageId: parsedIndex.id,
|
||||||
|
isChannelPost: true,
|
||||||
|
maxMessage: resolvedMaxMessage,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateInitialStateDisposable = (updateInitialState
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] updatedData in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let maxReadOutgoingMessageId = updatedData.maxReadOutgoingMessageId {
|
||||||
|
if let current = strongSelf.maxReadOutgoingMessageIdValue {
|
||||||
|
if maxReadOutgoingMessageId > current {
|
||||||
|
strongSelf.maxReadOutgoingMessageIdValue = maxReadOutgoingMessageId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.maxReadOutgoingMessageIdValue = maxReadOutgoingMessageId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -118,6 +217,7 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
self.initialStateDisposable?.dispose()
|
self.initialStateDisposable?.dispose()
|
||||||
self.holesDisposable?.dispose()
|
self.holesDisposable?.dispose()
|
||||||
self.readDisposable.dispose()
|
self.readDisposable.dispose()
|
||||||
|
self.updateInitialStateDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCurrentHole(entry: MessageHistoryHolesViewEntry?) {
|
func setCurrentHole(entry: MessageHistoryHolesViewEntry?) {
|
||||||
@ -129,7 +229,7 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if var currentHoles = strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] {
|
if var currentHoles = strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] {
|
||||||
currentHoles.subtract(removedHoleIndices)
|
currentHoles.subtract(removedHoleIndices.removedIndices)
|
||||||
strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] = currentHoles
|
strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] = currentHoles
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -139,11 +239,11 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<IndexSet, NoError> {
|
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<FetchMessageHistoryHoleResult, NoError> {
|
||||||
switch entry.hole {
|
switch entry.hole {
|
||||||
case let .peer(hole):
|
case let .peer(hole):
|
||||||
let fetchCount = min(entry.count, 100)
|
let fetchCount = min(entry.count, 100)
|
||||||
return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount)
|
return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerInput: .direct(peerId: hole.peerId, threadId: hole.threadId), namespace: hole.namespace, direction: entry.direction, space: entry.space, count: fetchCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +260,8 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
if let sourceMessage = transaction.getMessage(attribute.messageId) {
|
if let sourceMessage = transaction.getMessage(attribute.messageId) {
|
||||||
|
account.viewTracker.applyMaxReadIncomingMessageIdForReplyInfo(id: attribute.messageId, maxReadIncomingMessageId: messageIndex.id)
|
||||||
|
|
||||||
var updatedAttribute: ReplyThreadMessageAttribute?
|
var updatedAttribute: ReplyThreadMessageAttribute?
|
||||||
for i in 0 ..< sourceMessage.attributes.count {
|
for i in 0 ..< sourceMessage.attributes.count {
|
||||||
if let attribute = sourceMessage.attributes[i] as? ReplyThreadMessageAttribute {
|
if let attribute = sourceMessage.attributes[i] as? ReplyThreadMessageAttribute {
|
||||||
@ -257,10 +359,10 @@ public class ReplyThreadHistoryContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
|
public init(account: Account, peerId: PeerId, data: ChatReplyThreadMessage) {
|
||||||
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, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
return ReplyThreadHistoryContextImpl(queue: queue, account: account, data: data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,45 +373,51 @@ public class ReplyThreadHistoryContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ChatReplyThreadMessage {
|
public struct ChatReplyThreadMessage: Equatable {
|
||||||
public enum MaxMessage: Equatable {
|
|
||||||
case unknown
|
|
||||||
case known(MessageId?)
|
|
||||||
}
|
|
||||||
|
|
||||||
public var messageId: MessageId
|
public var messageId: MessageId
|
||||||
public var maxMessage: MaxMessage
|
public var isChannelPost: Bool
|
||||||
|
public var maxMessage: MessageId?
|
||||||
public var maxReadIncomingMessageId: MessageId?
|
public var maxReadIncomingMessageId: MessageId?
|
||||||
public var maxReadOutgoingMessageId: MessageId?
|
public var maxReadOutgoingMessageId: MessageId?
|
||||||
|
public var initialFilledHoles: IndexSet
|
||||||
|
|
||||||
public init(messageId: MessageId, maxMessage: MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
|
fileprivate init(messageId: MessageId, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, initialFilledHoles: IndexSet) {
|
||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
|
self.isChannelPost = isChannelPost
|
||||||
self.maxMessage = maxMessage
|
self.maxMessage = maxMessage
|
||||||
self.maxReadIncomingMessageId = maxReadIncomingMessageId
|
self.maxReadIncomingMessageId = maxReadIncomingMessageId
|
||||||
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
|
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
|
||||||
|
self.initialFilledHoles = initialFilledHoles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal<ChatReplyThreadMessage?, NoError> {
|
public enum FetchChannelReplyThreadMessageError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId, atMessageId: MessageId?) -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||||
}
|
}
|
||||||
|> mapToSignal { inputPeer -> Signal<ChatReplyThreadMessage?, NoError> in
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> mapToSignal { inputPeer -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
|
||||||
guard let inputPeer = inputPeer else {
|
guard let inputPeer = inputPeer else {
|
||||||
return .single(nil)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
let discussionMessage: Signal<Api.messages.DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
|
|
||||||
|
let replyInfo = Promise<AccountViewTracker.UpdatedMessageReplyInfo?>()
|
||||||
|
replyInfo.set(account.viewTracker.replyInfoForMessageId(messageId))
|
||||||
|
|
||||||
|
let remoteDiscussionMessageSignal: Signal<DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.messages.DiscussionMessage?, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.DiscussionMessage?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { discussionMessage -> Signal<DiscussionMessage?, NoError> in
|
||||||
return discussionMessage
|
|
||||||
|> mapToSignal { discussionMessage -> 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 -> DiscussionMessage? in
|
||||||
switch discussionMessage {
|
switch discussionMessage {
|
||||||
case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users):
|
case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users):
|
||||||
let parsedMessages = messages.compactMap { message -> StoreMessage? in
|
let parsedMessages = messages.compactMap { message -> StoreMessage? in
|
||||||
@ -344,19 +452,20 @@ 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: MessageId?
|
||||||
if let maxId = maxId {
|
if let maxId = maxId {
|
||||||
resolvedMaxMessage = .known(MessageId(
|
resolvedMaxMessage = MessageId(
|
||||||
peerId: parsedIndex.id.peerId,
|
peerId: parsedIndex.id.peerId,
|
||||||
namespace: Namespaces.Message.Cloud,
|
namespace: Namespaces.Message.Cloud,
|
||||||
id: maxId
|
id: maxId
|
||||||
))
|
)
|
||||||
} else {
|
} else {
|
||||||
resolvedMaxMessage = .known(nil)
|
resolvedMaxMessage = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChatReplyThreadMessage(
|
return DiscussionMessage(
|
||||||
messageId: parsedIndex.id,
|
messageId: parsedIndex.id,
|
||||||
|
isChannelPost: true,
|
||||||
maxMessage: resolvedMaxMessage,
|
maxMessage: resolvedMaxMessage,
|
||||||
maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in
|
maxReadIncomingMessageId: readInboxMaxId.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)
|
||||||
@ -368,5 +477,171 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let discussionMessageSignal = (replyInfo.get()
|
||||||
|
|> take(1)
|
||||||
|
|> mapToSignal { replyInfo -> Signal<DiscussionMessage?, NoError> in
|
||||||
|
guard let replyInfo = replyInfo else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> DiscussionMessage? in
|
||||||
|
var foundDiscussionMessageId: MessageId?
|
||||||
|
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
|
||||||
|
for attribute in attributes {
|
||||||
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
|
if attribute.messageId == messageId {
|
||||||
|
foundDiscussionMessageId = id
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundDiscussionMessageId != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
guard let discussionMessageId = foundDiscussionMessageId else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return DiscussionMessage(
|
||||||
|
messageId: discussionMessageId,
|
||||||
|
isChannelPost: true,
|
||||||
|
maxMessage: replyInfo.maxMessageId,
|
||||||
|
maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId,
|
||||||
|
maxReadOutgoingMessageId: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> mapToSignal { result -> Signal<DiscussionMessage?, NoError> in
|
||||||
|
if let result = result {
|
||||||
|
return .single(result)
|
||||||
|
} else {
|
||||||
|
return remoteDiscussionMessageSignal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let discussionMessage = Promise<DiscussionMessage?>()
|
||||||
|
discussionMessage.set(discussionMessageSignal)
|
||||||
|
|
||||||
|
let preloadedHistoryPosition: Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, MessageId?, MessageId?), FetchChannelReplyThreadMessageError> = replyInfo.get()
|
||||||
|
|> take(1)
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> mapToSignal { replyInfo -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, MessageId?, MessageId?), FetchChannelReplyThreadMessageError> in
|
||||||
|
if let replyInfo = replyInfo {
|
||||||
|
return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, MessageId?, MessageId?) in
|
||||||
|
var threadInput: FetchMessageHistoryHoleThreadInput = .threadFromChannel(channelMessageId: messageId)
|
||||||
|
var threadMessageId: MessageId?
|
||||||
|
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
|
||||||
|
for attribute in attributes {
|
||||||
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
|
if attribute.messageId == messageId {
|
||||||
|
threadMessageId = id
|
||||||
|
threadInput = .direct(peerId: id.peerId, threadId: makeMessageThreadId(id))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return (threadInput, replyInfo.commentsPeerId, threadMessageId, atMessageId ?? replyInfo.maxReadIncomingMessageId, replyInfo.maxMessageId)
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
} else {
|
||||||
|
return discussionMessage.get()
|
||||||
|
|> take(1)
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> mapToSignal { discussionMessage -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, MessageId?, MessageId?), FetchChannelReplyThreadMessageError> in
|
||||||
|
guard let discussionMessage = discussionMessage else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
let topMessageId = discussionMessage.messageId
|
||||||
|
let commentsPeerId = topMessageId.peerId
|
||||||
|
return .single((.direct(peerId: commentsPeerId, threadId: makeMessageThreadId(topMessageId)), commentsPeerId, discussionMessage.messageId, atMessageId ?? discussionMessage.maxReadIncomingMessageId, discussionMessage.maxMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let preloadedHistory = preloadedHistoryPosition
|
||||||
|
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, aroundMessageId, maxMessageId -> Signal<FetchMessageHistoryHoleResult, FetchChannelReplyThreadMessageError> in
|
||||||
|
guard let maxMessageId = maxMessageId else {
|
||||||
|
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(integersIn: 1 ..< Int(Int32.max - 1)), strictRemovedIndices: IndexSet()))
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> Signal<FetchMessageHistoryHoleResult, FetchChannelReplyThreadMessageError> in
|
||||||
|
if let threadMessageId = threadMessageId {
|
||||||
|
var holes = transaction.getThreadIndexHoles(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId), namespace: Namespaces.Message.Cloud)
|
||||||
|
holes.remove(integersIn: Int(maxMessageId.id + 1) ..< Int(Int32.max))
|
||||||
|
|
||||||
|
let isParticipant = transaction.getPeerChatListIndex(commentsPeerId) != nil
|
||||||
|
if isParticipant {
|
||||||
|
let historyHoles = transaction.getHoles(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud)
|
||||||
|
holes.formIntersection(historyHoles)
|
||||||
|
}
|
||||||
|
|
||||||
|
let anchor: HistoryViewInputAnchor
|
||||||
|
if let aroundMessageId = aroundMessageId {
|
||||||
|
anchor = .message(aroundMessageId)
|
||||||
|
} else {
|
||||||
|
anchor = .upperBound
|
||||||
|
}
|
||||||
|
|
||||||
|
let testView = transaction.getMessagesHistoryViewState(
|
||||||
|
input: .external(MessageHistoryViewExternalInput(
|
||||||
|
peerId: commentsPeerId,
|
||||||
|
threadId: makeMessageThreadId(threadMessageId),
|
||||||
|
maxReadIncomingMessageId: nil,
|
||||||
|
maxReadOutgoingMessageId: nil,
|
||||||
|
holes: [
|
||||||
|
Namespaces.Message.Cloud: holes
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
count: 30,
|
||||||
|
clipHoles: true,
|
||||||
|
anchor: anchor,
|
||||||
|
namespaces: .not(Namespaces.Message.allScheduled)
|
||||||
|
)
|
||||||
|
if !testView.isLoading {
|
||||||
|
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let direction: MessageHistoryViewRelativeHoleDirection
|
||||||
|
if let aroundMessageId = aroundMessageId {
|
||||||
|
direction = .aroundId(aroundMessageId)
|
||||||
|
} else {
|
||||||
|
direction = .range(start: MessageId(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud, id: Int32.max - 1), end: MessageId(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud, id: 1))
|
||||||
|
}
|
||||||
|
return fetchMessageHistoryHole(accountPeerId: account.peerId, source: .network(account.network), postbox: account.postbox, peerInput: peerInput, namespace: Namespaces.Message.Cloud, direction: direction, space: .everywhere, count: 30)
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineLatest(
|
||||||
|
discussionMessage.get()
|
||||||
|
|> take(1)
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self),
|
||||||
|
preloadedHistory
|
||||||
|
)
|
||||||
|
|> mapToSignal { discussionMessage, initialFilledHoles -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
|
||||||
|
guard let discussionMessage = discussionMessage else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
|
||||||
|
for range in initialFilledHoles.strictRemovedIndices.rangeView {
|
||||||
|
transaction.removeThreadIndexHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound))
|
||||||
|
}
|
||||||
|
|
||||||
|
return .single(ChatReplyThreadMessage(
|
||||||
|
messageId: discussionMessage.messageId,
|
||||||
|
isChannelPost: discussionMessage.isChannelPost,
|
||||||
|
maxMessage: discussionMessage.maxMessage,
|
||||||
|
maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId,
|
||||||
|
maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId,
|
||||||
|
initialFilledHoles: initialFilledHoles.removedIndices
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,14 +539,13 @@ func fetchRemoteMessage(postbox: Postbox, source: FetchMessageHistoryHoleSource,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timestamp: Int32) -> Signal<MessageId?, NoError> {
|
public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, threadId: Int64?, timestamp: Int32) -> Signal<MessageId?, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<MessageId?, NoError> in
|
return account.postbox.transaction { transaction -> Signal<MessageId?, NoError> in
|
||||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
return .single(transaction.findClosestMessageIdByTimestamp(peerId: peerId, timestamp: timestamp))
|
return .single(transaction.findClosestMessageIdByTimestamp(peerId: peerId, timestamp: timestamp))
|
||||||
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var secondaryIndex: Signal<MessageIndex?, NoError> = .single(nil)
|
if let threadId = threadId {
|
||||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, let migrationReference = cachedData.migrationReference, let secondaryPeer = transaction.getPeer(migrationReference.maxMessageId.peerId), let inputSecondaryPeer = apiInputPeer(secondaryPeer) {
|
let primaryIndex = account.network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: makeThreadIdMessageId(peerId: peerId, threadId: threadId).id, offsetId: 0, offsetDate: timestamp, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||||
secondaryIndex = account.network.request(Api.functions.messages.getHistory(peer: inputSecondaryPeer, offsetId: 0, offsetDate: timestamp, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
|
||||||
|> map { result -> MessageIndex? in
|
|> map { result -> MessageIndex? in
|
||||||
let messages: [Api.Message]
|
let messages: [Api.Message]
|
||||||
switch result {
|
switch result {
|
||||||
@ -569,45 +568,76 @@ public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timesta
|
|||||||
|> `catch` { _ -> Signal<MessageIndex?, NoError> in
|
|> `catch` { _ -> Signal<MessageIndex?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
}
|
return primaryIndex
|
||||||
let primaryIndex = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 0, offsetDate: timestamp, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
|> map { primaryIndex -> MessageId? in
|
||||||
|> map { result -> MessageIndex? in
|
return primaryIndex?.id
|
||||||
let messages: [Api.Message]
|
|
||||||
switch result {
|
|
||||||
case let .messages(apiMessages, _, _):
|
|
||||||
messages = apiMessages
|
|
||||||
case let .channelMessages(_, _, _, apiMessages, _, _):
|
|
||||||
messages = apiMessages
|
|
||||||
case let .messagesSlice(_, _, _, apiMessages, _, _):
|
|
||||||
messages = apiMessages
|
|
||||||
case .messagesNotModified:
|
|
||||||
messages = []
|
|
||||||
}
|
}
|
||||||
for message in messages {
|
} else {
|
||||||
if let message = StoreMessage(apiMessage: message) {
|
var secondaryIndex: Signal<MessageIndex?, NoError> = .single(nil)
|
||||||
return message.index
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, let migrationReference = cachedData.migrationReference, let secondaryPeer = transaction.getPeer(migrationReference.maxMessageId.peerId), let inputSecondaryPeer = apiInputPeer(secondaryPeer) {
|
||||||
|
secondaryIndex = account.network.request(Api.functions.messages.getHistory(peer: inputSecondaryPeer, offsetId: 0, offsetDate: timestamp, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||||
|
|> map { result -> MessageIndex? in
|
||||||
|
let messages: [Api.Message]
|
||||||
|
switch result {
|
||||||
|
case let .messages(apiMessages, _, _):
|
||||||
|
messages = apiMessages
|
||||||
|
case let .channelMessages(_, _, _, apiMessages, _, _):
|
||||||
|
messages = apiMessages
|
||||||
|
case let .messagesSlice(_, _, _, apiMessages, _, _):
|
||||||
|
messages = apiMessages
|
||||||
|
case .messagesNotModified:
|
||||||
|
messages = []
|
||||||
|
}
|
||||||
|
for message in messages {
|
||||||
|
if let message = StoreMessage(apiMessage: message) {
|
||||||
|
return message.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|> `catch` { _ -> Signal<MessageIndex?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
let primaryIndex = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 0, offsetDate: timestamp, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||||
}
|
|> map { result -> MessageIndex? in
|
||||||
|> `catch` { _ -> Signal<MessageIndex?, NoError> in
|
let messages: [Api.Message]
|
||||||
return .single(nil)
|
switch result {
|
||||||
}
|
case let .messages(apiMessages, _, _):
|
||||||
return combineLatest(primaryIndex, secondaryIndex)
|
messages = apiMessages
|
||||||
|> map { primaryIndex, secondaryIndex -> MessageId? in
|
case let .channelMessages(_, _, _, apiMessages, _, _):
|
||||||
if let primaryIndex = primaryIndex, let secondaryIndex = secondaryIndex {
|
messages = apiMessages
|
||||||
if abs(primaryIndex.timestamp - timestamp) < abs(secondaryIndex.timestamp - timestamp) {
|
case let .messagesSlice(_, _, _, apiMessages, _, _):
|
||||||
return primaryIndex.id
|
messages = apiMessages
|
||||||
} else {
|
case .messagesNotModified:
|
||||||
return secondaryIndex.id
|
messages = []
|
||||||
|
}
|
||||||
|
for message in messages {
|
||||||
|
if let message = StoreMessage(apiMessage: message) {
|
||||||
|
return message.index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let primaryIndex = primaryIndex {
|
|
||||||
return primaryIndex.id
|
|
||||||
} else if let secondaryIndex = secondaryIndex {
|
|
||||||
return secondaryIndex.id
|
|
||||||
} else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|> `catch` { _ -> Signal<MessageIndex?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return combineLatest(primaryIndex, secondaryIndex)
|
||||||
|
|> map { primaryIndex, secondaryIndex -> MessageId? in
|
||||||
|
if let primaryIndex = primaryIndex, let secondaryIndex = secondaryIndex {
|
||||||
|
if abs(primaryIndex.timestamp - timestamp) < abs(secondaryIndex.timestamp - timestamp) {
|
||||||
|
return primaryIndex.id
|
||||||
|
} else {
|
||||||
|
return secondaryIndex.id
|
||||||
|
}
|
||||||
|
} else if let primaryIndex = primaryIndex {
|
||||||
|
return primaryIndex.id
|
||||||
|
} else if let secondaryIndex = secondaryIndex {
|
||||||
|
return secondaryIndex.id
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -626,19 +656,26 @@ public func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerRefe
|
|||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<Peer, UpdatedRemotePeerError> in
|
|> mapToSignal { result -> Signal<Peer, UpdatedRemotePeerError> in
|
||||||
if let updatedPeer = result.first.flatMap(TelegramUser.init(user:)), updatedPeer.id == peer.id {
|
guard let apiUser = result.first else {
|
||||||
return postbox.transaction { transaction -> Peer in
|
|
||||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
return updatedPeer
|
|
||||||
}
|
|
||||||
|> mapError { _ -> UpdatedRemotePeerError in
|
|
||||||
return .generic
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
return postbox.transaction { transaction -> Peer? in
|
||||||
|
guard let peer = TelegramUser.merge(transaction.getPeer(apiUser.peerId) as? TelegramUser, rhs: apiUser) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
return peer
|
||||||
|
}
|
||||||
|
|> castError(UpdatedRemotePeerError.self)
|
||||||
|
|> mapToSignal { peer -> Signal<Peer, UpdatedRemotePeerError> in
|
||||||
|
if let peer = peer {
|
||||||
|
return .single(peer)
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if case let .group(id) = peer {
|
} else if case let .group(id) = peer {
|
||||||
return network.request(Api.functions.messages.getChats(id: [id]))
|
return network.request(Api.functions.messages.getChats(id: [id]))
|
||||||
|
@ -8,7 +8,7 @@ func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMedia
|
|||||||
var representations: [TelegramMediaImageRepresentation] = []
|
var representations: [TelegramMediaImageRepresentation] = []
|
||||||
switch photo {
|
switch photo {
|
||||||
case let .userProfilePhoto(flags, _, photoSmall, photoBig, dcId):
|
case let .userProfilePhoto(flags, _, photoSmall, photoBig, dcId):
|
||||||
let hasVideo = (flags & (1 << 0)) != 0
|
let _ = (flags & (1 << 0)) != 0
|
||||||
|
|
||||||
let smallResource: TelegramMediaResource
|
let smallResource: TelegramMediaResource
|
||||||
let fullSizeResource: TelegramMediaResource
|
let fullSizeResource: TelegramMediaResource
|
||||||
@ -138,4 +138,37 @@ extension TelegramUser {
|
|||||||
return TelegramUser(user: rhs)
|
return TelegramUser(user: rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func merge(lhs: TelegramUser?, rhs: TelegramUser) -> TelegramUser {
|
||||||
|
guard let lhs = lhs else {
|
||||||
|
return rhs
|
||||||
|
}
|
||||||
|
if case .personal = rhs.accessHash {
|
||||||
|
return rhs
|
||||||
|
} else {
|
||||||
|
var userFlags: UserInfoFlags = []
|
||||||
|
if rhs.flags.contains(.isVerified) {
|
||||||
|
userFlags.insert(.isVerified)
|
||||||
|
}
|
||||||
|
if rhs.flags.contains(.isSupport) {
|
||||||
|
userFlags.insert(.isSupport)
|
||||||
|
}
|
||||||
|
if rhs.flags.contains(.isScam) {
|
||||||
|
userFlags.insert(.isScam)
|
||||||
|
}
|
||||||
|
|
||||||
|
let botInfo: BotUserInfo? = rhs.botInfo
|
||||||
|
|
||||||
|
let restrictionInfo: PeerAccessRestrictionInfo? = rhs.restrictionInfo
|
||||||
|
|
||||||
|
let accessHash: TelegramPeerAccessHash?
|
||||||
|
if let rhsAccessHashValue = lhs.accessHash, case .personal = rhsAccessHashValue {
|
||||||
|
accessHash = rhsAccessHashValue
|
||||||
|
} else {
|
||||||
|
accessHash = lhs.accessHash ?? rhs.accessHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: rhs.username, phone: lhs.phone, photo: rhs.photo.isEmpty ? lhs.photo : rhs.photo, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,9 @@ public func updateAccountPeerName(account: Account, firstName: String, lastName:
|
|||||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
if let result = result {
|
if let result = result {
|
||||||
let peer = TelegramUser(user: result)
|
if let peer = TelegramUser.merge(transaction.getPeer(result.peerId) as? TelegramUser, rhs: result) {
|
||||||
updatePeers(transaction: transaction, peers: [peer], update: { $1 })
|
updatePeers(transaction: transaction, peers: [peer], update: { $1 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,6 +44,7 @@ public func updateAbout(account: Account, about: String?) -> Signal<Void, Update
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} |> mapError { _ -> UpdateAboutError in return .generic }
|
}
|
||||||
|
|> castError(UpdateAboutError.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,13 +173,14 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
return postbox.transaction { transaction -> Bool in
|
return postbox.transaction { transaction -> Bool in
|
||||||
switch result {
|
switch result {
|
||||||
case let .userFull(userFull):
|
case let .userFull(userFull):
|
||||||
let telegramUser = TelegramUser(user: userFull.user)
|
if let telegramUser = TelegramUser.merge(transaction.getPeer(userFull.user.peerId) as? TelegramUser, rhs: userFull.user) {
|
||||||
updatePeers(transaction: transaction, peers: [telegramUser], update: { _, updated -> Peer in
|
updatePeers(transaction: transaction, peers: [telegramUser], update: { _, updated -> Peer in
|
||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
|
}
|
||||||
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFull.notifySettings)])
|
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFull.notifySettings)])
|
||||||
if let presence = TelegramUserPresence(apiUser: userFull.user) {
|
if let presence = TelegramUserPresence(apiUser: userFull.user) {
|
||||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: [telegramUser.id: presence])
|
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: [userFull.user.peerId: presence])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, current in
|
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, current in
|
||||||
@ -267,10 +268,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||||
peers.append(telegramUser)
|
peers.append(telegramUser)
|
||||||
if let presence = TelegramUserPresence(apiUser: user) {
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
peerPresences[telegramUser.id] = presence
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,10 +418,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||||
peers.append(telegramUser)
|
peers.append(telegramUser)
|
||||||
if let presence = TelegramUserPresence(apiUser: user) {
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
peerPresences[telegramUser.id] = presence
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,10 +430,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
|||||||
switch participantResult {
|
switch participantResult {
|
||||||
case let .channelParticipant(_, users):
|
case let .channelParticipant(_, users):
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||||
peers.append(telegramUser)
|
peers.append(telegramUser)
|
||||||
if let presence = TelegramUserPresence(apiUser: user) {
|
if let presence = TelegramUserPresence(apiUser: user) {
|
||||||
peerPresences[telegramUser.id] = presence
|
peerPresences[telegramUser.id] = presence
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,17 @@ func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId]) {
|
struct ReplyThreadUserMessage {
|
||||||
|
var id: PeerId
|
||||||
|
var messageId: MessageId
|
||||||
|
var isOutgoing: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [ReplyThreadUserMessage]) {
|
||||||
updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: threadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: false)
|
updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: threadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMessageThreadStatsInternal(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId], allowChannel: Bool) {
|
private func updateMessageThreadStatsInternal(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [ReplyThreadUserMessage], allowChannel: Bool) {
|
||||||
guard let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel else {
|
guard let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -77,7 +83,24 @@ private func updateMessageThreadStatsInternal(transaction: Transaction, threadMe
|
|||||||
loop: for j in 0 ..< attributes.count {
|
loop: for j in 0 ..< attributes.count {
|
||||||
if let attribute = attributes[j] as? ReplyThreadMessageAttribute {
|
if let attribute = attributes[j] as? ReplyThreadMessageAttribute {
|
||||||
let count = max(0, attribute.count + countDifference)
|
let count = max(0, attribute.count + countDifference)
|
||||||
attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId, maxMessageId: attribute.maxMessageId, maxReadMessageId: attribute.maxReadMessageId)
|
var maxMessageId = attribute.maxMessageId
|
||||||
|
var maxReadMessageId = attribute.maxReadMessageId
|
||||||
|
if let maxAddedId = addedMessagePeers.map(\.messageId.id).max() {
|
||||||
|
if let currentMaxMessageId = maxMessageId {
|
||||||
|
maxMessageId = max(currentMaxMessageId, maxAddedId)
|
||||||
|
} else {
|
||||||
|
maxMessageId = maxAddedId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let maxAddedReadId = addedMessagePeers.filter(\.isOutgoing).map(\.messageId.id).max() {
|
||||||
|
if let currentMaxMessageId = maxReadMessageId {
|
||||||
|
maxReadMessageId = max(currentMaxMessageId, maxAddedReadId)
|
||||||
|
} else {
|
||||||
|
maxReadMessageId = maxAddedReadId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers.map(\.id), isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId, maxMessageId: maxMessageId, maxReadMessageId: maxReadMessageId)
|
||||||
} else if let attribute = attributes[j] as? SourceReferenceMessageAttribute {
|
} else if let attribute = attributes[j] as? SourceReferenceMessageAttribute {
|
||||||
channelThreadMessageId = attribute.messageId
|
channelThreadMessageId = attribute.messageId
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,14 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?,
|
|||||||
transaction.updatePeersInternal(peers, update: { previous, updated in
|
transaction.updatePeersInternal(peers, update: { previous, updated in
|
||||||
let peerId = updated.id
|
let peerId = updated.id
|
||||||
|
|
||||||
|
var updated = updated
|
||||||
|
|
||||||
|
if let previous = previous as? TelegramUser, let updatedUser = updated as? TelegramUser {
|
||||||
|
updated = TelegramUser.merge(lhs: previous, rhs: updatedUser)
|
||||||
|
} else if let previous = previous as? TelegramChannel, let updatedChannel = updated as? TelegramChannel {
|
||||||
|
updated = mergeChannel(lhs: previous, rhs: updatedChannel)
|
||||||
|
}
|
||||||
|
|
||||||
switch peerId.namespace {
|
switch peerId.namespace {
|
||||||
case Namespaces.Peer.CloudUser:
|
case Namespaces.Peer.CloudUser:
|
||||||
break
|
break
|
||||||
|
@ -303,9 +303,9 @@ 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, maxReadIncomingMessageId, maxReadOutgoingMessageId):
|
case let .replyThread(data):
|
||||||
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
let context = chatLocationContext(holder: contextHolder, account: self.account, data: data)
|
||||||
return .external(messageId.peerId, context.state)
|
return .external(data.messageId.peerId, context.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,8 +313,8 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
switch location {
|
switch location {
|
||||||
case .peer:
|
case .peer:
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId):
|
case let .replyThread(data):
|
||||||
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
let context = chatLocationContext(holder: contextHolder, account: self.account, data: data)
|
||||||
return context.maxReadOutgoingMessageId
|
return context.maxReadOutgoingMessageId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,19 +323,19 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
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, maxReadIncomingMessageId, maxReadOutgoingMessageId):
|
case let .replyThread(data):
|
||||||
let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
let context = chatLocationContext(holder: contextHolder, account: self.account, data: data)
|
||||||
context.applyMaxReadIndex(messageIndex: messageIndex)
|
context.applyMaxReadIndex(messageIndex: messageIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) -> ReplyThreadHistoryContext {
|
private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, account: Account, data: ChatReplyThreadMessage) -> 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, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
return ChatLocationContextHolderImpl(account: account, data: data)
|
||||||
}
|
}
|
||||||
} as! ChatLocationContextHolderImpl
|
} as! ChatLocationContextHolderImpl
|
||||||
return holder.context
|
return holder.context
|
||||||
@ -344,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, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) {
|
init(account: Account, data: ChatReplyThreadMessage) {
|
||||||
self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId)
|
self.context = ReplyThreadHistoryContext(account: account, peerId: data.messageId.peerId, data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +69,8 @@ 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(replyThreadMessage):
|
||||||
return messageId.peerId
|
return replyThreadMessage.messageId.peerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,16 +371,16 @@ 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(replyThreadMessage):
|
||||||
locationBroadcastPanelSource = .none
|
locationBroadcastPanelSource = .none
|
||||||
let promise = Promise<Message?>()
|
let promise = Promise<Message?>()
|
||||||
let key = PostboxViewKey.messages([messageId])
|
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
||||||
promise.set(context.account.postbox.combinedView(keys: [key])
|
promise.set(context.account.postbox.combinedView(keys: [key])
|
||||||
|> map { views -> Message? in
|
|> map { views -> Message? in
|
||||||
guard let view = views.views[key] as? MessagesView else {
|
guard let view = views.views[key] as? MessagesView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return view.messages[messageId]
|
return view.messages[replyThreadMessage.messageId]
|
||||||
})
|
})
|
||||||
self.chatLocationInfoData = .replyThread(promise)
|
self.chatLocationInfoData = .replyThread(promise)
|
||||||
}
|
}
|
||||||
@ -481,8 +481,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
var openChatLocation = strongSelf.chatLocation
|
var openChatLocation = strongSelf.chatLocation
|
||||||
if case let .replyThread(messageId, _, _, _, _) = openChatLocation {
|
if case let .replyThread(replyThreadMessage) = openChatLocation {
|
||||||
if message.threadId != makeMessageThreadId(messageId) {
|
if message.threadId != makeMessageThreadId(replyThreadMessage.messageId) {
|
||||||
openChatLocation = .peer(message.id.peerId)
|
openChatLocation = .peer(message.id.peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1601,7 +1601,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if case .replyThread(message.id, _, _, _, _) = strongSelf.chatLocation {
|
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.messageId == message.id {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1640,8 +1640,8 @@ 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(replyThreadMessage):
|
||||||
let peerId = messageId.peerId
|
let peerId = replyThreadMessage.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)
|
||||||
}
|
}
|
||||||
}, requestRedeliveryOfFailedMessages: { [weak self] id in
|
}, requestRedeliveryOfFailedMessages: { [weak self] id in
|
||||||
@ -2191,11 +2191,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
strongSelf.openMessageReplies(messageId: messageId, isChannelPost: isChannelPost, atMessage: nil)
|
||||||
ChatControllerImpl.openMessageReplies(context: strongSelf.context, navigationController: navigationController, present: { c, a in
|
|
||||||
self?.present(c, in: .window(.root), with: a)
|
|
||||||
}, messageId: messageId, isChannelPost: isChannelPost, atMessage: nil)
|
|
||||||
}
|
|
||||||
}, openReplyThreadOriginalMessage: { [weak self] message in
|
}, openReplyThreadOriginalMessage: { [weak self] message in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -2211,9 +2207,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
if let threadMessageId = threadMessageId {
|
if let threadMessageId = threadMessageId {
|
||||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||||
ChatControllerImpl.openMessageReplies(context: strongSelf.context, navigationController: navigationController, present: { c, a in
|
strongSelf.openMessageReplies(messageId: threadMessageId, isChannelPost: true, atMessage: attribute.messageId)
|
||||||
self?.present(c, in: .window(.root), with: a)
|
|
||||||
}, messageId: threadMessageId, isChannelPost: true, atMessage: attribute.messageId)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId))
|
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId))
|
||||||
@ -2614,7 +2608,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if strongSelf.preloadHistoryPeerId != peerDiscussionId {
|
if strongSelf.preloadHistoryPeerId != peerDiscussionId {
|
||||||
strongSelf.preloadHistoryPeerId = peerDiscussionId
|
strongSelf.preloadHistoryPeerId = peerDiscussionId
|
||||||
if let peerDiscussionId = peerDiscussionId {
|
if let peerDiscussionId = peerDiscussionId {
|
||||||
strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
|
let combinedDisposable = DisposableSet()
|
||||||
|
strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable)
|
||||||
|
combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: peerDiscussionId).start())
|
||||||
|
combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
|
||||||
} else {
|
} else {
|
||||||
strongSelf.preloadHistoryPeerIdDisposable.set(nil)
|
strongSelf.preloadHistoryPeerIdDisposable.set(nil)
|
||||||
}
|
}
|
||||||
@ -2642,18 +2639,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData {
|
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData {
|
||||||
let onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
|
|
||||||
let hasScheduledMessages: Signal<Bool, NoError> = .single(false)
|
let hasScheduledMessages: Signal<Bool, NoError> = .single(false)
|
||||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||||
|
|
||||||
let isReplyThread: Bool
|
|
||||||
let replyThreadType: ChatTitleContent.ReplyThreadType
|
let replyThreadType: ChatTitleContent.ReplyThreadType
|
||||||
switch chatLocation {
|
switch chatLocation {
|
||||||
case .peer:
|
case .peer:
|
||||||
replyThreadType = .replies
|
replyThreadType = .replies
|
||||||
case let .replyThread(_, isChannelPost, _, _, _):
|
case let .replyThread(replyThreadMessage):
|
||||||
isReplyThread = true
|
if replyThreadMessage.isChannelPost {
|
||||||
if isChannelPost {
|
|
||||||
replyThreadType = .comments
|
replyThreadType = .comments
|
||||||
} else {
|
} else {
|
||||||
replyThreadType = .replies
|
replyThreadType = .replies
|
||||||
@ -2997,8 +2991,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
switch self.chatLocation {
|
switch self.chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
activitySpace = PeerActivitySpace(peerId: peerId, threadId: nil)
|
activitySpace = PeerActivitySpace(peerId: peerId, threadId: nil)
|
||||||
case let .replyThread(threadMessageId, _, _, _, _):
|
case let .replyThread(replyThreadMessage):
|
||||||
activitySpace = PeerActivitySpace(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId))
|
activitySpace = PeerActivitySpace(peerId: replyThreadMessage.messageId.peerId, threadId: makeMessageThreadId(replyThreadMessage.messageId))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inputActivityDisposable = (self.typingActivityPromise.get()
|
self.inputActivityDisposable = (self.typingActivityPromise.get()
|
||||||
@ -3416,11 +3410,11 @@ 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(replyThreadMessage) = strongSelf.chatLocation {
|
||||||
if isTopReplyThreadMessageShown {
|
if isTopReplyThreadMessageShown {
|
||||||
pinnedMessageId = nil
|
pinnedMessageId = nil
|
||||||
} else {
|
} else {
|
||||||
pinnedMessageId = messageId
|
pinnedMessageId = replyThreadMessage.messageId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4266,7 +4260,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, openCalendarSearch: { [weak self] in
|
}, openCalendarSearch: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let peerId = strongSelf.chatLocation.peerId
|
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
|
|
||||||
let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in
|
let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in
|
||||||
@ -4274,7 +4267,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.loadingMessage.set(true)
|
strongSelf.loadingMessage.set(true)
|
||||||
strongSelf.messageIndexDisposable.set((searchMessageIdByTimestamp(account: strongSelf.context.account, peerId: peerId, timestamp: timestamp) |> deliverOnMainQueue).start(next: { messageId in
|
|
||||||
|
let peerId: PeerId
|
||||||
|
let threadId: Int64?
|
||||||
|
switch strongSelf.chatLocation {
|
||||||
|
case let .peer(peerIdValue):
|
||||||
|
peerId = peerIdValue
|
||||||
|
threadId = nil
|
||||||
|
case let .replyThread(replyThreadMessage):
|
||||||
|
peerId = replyThreadMessage.messageId.peerId
|
||||||
|
threadId = makeMessageThreadId(replyThreadMessage.messageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.messageIndexDisposable.set((searchMessageIdByTimestamp(account: strongSelf.context.account, peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).start(next: { messageId in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(false)
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
@ -5098,6 +5103,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId)
|
unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId)
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
|
}, scrollToTop: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory()
|
||||||
}, viewReplies: { [weak self] sourceMessageId, replyThreadResult in
|
}, viewReplies: { [weak self] sourceMessageId, replyThreadResult in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -5105,7 +5116,7 @@ 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, maxReadIncomingMessageId: replyThreadResult.maxReadIncomingMessageId, maxReadOutgoingMessageId: replyThreadResult.maxReadOutgoingMessageId), subject: subject, keepStack: .always))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadResult), 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()))
|
||||||
|
|
||||||
@ -5168,8 +5179,8 @@ 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: [:])
|
||||||
var activityThreadId: Int64?
|
var activityThreadId: Int64?
|
||||||
if case let .replyThread(messageId, _, _, _, _) = self.chatLocation {
|
if case let .replyThread(replyThreadMessage) = self.chatLocation {
|
||||||
activityThreadId = makeMessageThreadId(messageId)
|
activityThreadId = makeMessageThreadId(replyThreadMessage.messageId)
|
||||||
}
|
}
|
||||||
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, threadId: activityThreadId))
|
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
|
||||||
@ -7566,8 +7577,8 @@ 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(replyThreadMessage):
|
||||||
defaultReplyMessageId = messageId
|
defaultReplyMessageId = replyThreadMessage.messageId
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages.map { message in
|
return messages.map { message in
|
||||||
@ -7609,13 +7620,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
||||||
let peerId: PeerId
|
let peerId: PeerId = self.chatLocation.peerId
|
||||||
switch self.chatLocation {
|
|
||||||
case let .peer(peerIdValue):
|
|
||||||
peerId = peerIdValue
|
|
||||||
case let .replyThread(messageId, _, _, _, _):
|
|
||||||
peerId = messageId.peerId
|
|
||||||
}
|
|
||||||
|
|
||||||
if commit || !self.presentationInterfaceState.isScheduledMessages {
|
if commit || !self.presentationInterfaceState.isScheduledMessages {
|
||||||
self.commitPurposefulAction()
|
self.commitPurposefulAction()
|
||||||
@ -8035,8 +8040,8 @@ 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(replyThreadMessage):
|
||||||
searchTopMsgId = messageId
|
searchTopMsgId = replyThreadMessage.messageId
|
||||||
}
|
}
|
||||||
switch search.domain {
|
switch search.domain {
|
||||||
case .everything:
|
case .everything:
|
||||||
@ -8217,7 +8222,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
progressDisposable.dispose()
|
progressDisposable.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] index in
|
|> deliverOnMainQueue).start(next: { index in
|
||||||
if index.1 {
|
if index.1 {
|
||||||
if !progressStarted {
|
if !progressStarted {
|
||||||
progressStarted = true
|
progressStarted = true
|
||||||
@ -8238,6 +8243,70 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollToStartOfHistory() {
|
||||||
|
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true), id: 0)
|
||||||
|
|
||||||
|
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||||
|
let signal = historyView
|
||||||
|
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||||
|
switch historyView {
|
||||||
|
case .Loading:
|
||||||
|
return .single((nil, true))
|
||||||
|
case .HistoryView:
|
||||||
|
return .single((nil, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(until: { index in
|
||||||
|
return SignalTakeAction(passthrough: true, complete: !index.1)
|
||||||
|
})
|
||||||
|
|
||||||
|
var cancelImpl: (() -> Void)?
|
||||||
|
let presentationData = self.presentationData
|
||||||
|
let displayTime = CACurrentMediaTime()
|
||||||
|
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
if CACurrentMediaTime() - displayTime > 1.5 {
|
||||||
|
cancelImpl?()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
self?.present(controller, in: .window(.root))
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.05, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = MetaDisposable()
|
||||||
|
var progressStarted = false
|
||||||
|
self.messageIndexDisposable.set((signal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { index in
|
||||||
|
if index.1 {
|
||||||
|
if !progressStarted {
|
||||||
|
progressStarted = true
|
||||||
|
progressDisposable.set(progressSignal.start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, completed: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.loadingMessage.set(false)
|
||||||
|
strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
cancelImpl = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.loadingMessage.set(false)
|
||||||
|
strongSelf.messageIndexDisposable.set(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateDownButtonVisibility() {
|
func updateDownButtonVisibility() {
|
||||||
let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil
|
let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil
|
||||||
self.chatDisplayNode.navigateButtons.displayDownButton = self.shouldDisplayDownButton && !recordingMediaMessage
|
self.chatDisplayNode.navigateButtons.displayDownButton = self.shouldDisplayDownButton && !recordingMediaMessage
|
||||||
@ -8251,25 +8320,68 @@ 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?) {
|
private func openMessageReplies(messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?) {
|
||||||
let foundIndex = Promise<ReplyThreadInfo?>()
|
guard let navigationController = self.navigationController as? NavigationController else {
|
||||||
foundIndex.set(fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId)))
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let progressSignal: Signal<Never, NoError> = Signal { [weak self] _ in
|
||||||
|
guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
var cancelImpl: (() -> Void)?
|
let previousId = controllerInteraction.currentMessageWithLoadingReplyThread
|
||||||
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
controllerInteraction.currentMessageWithLoadingReplyThread = messageId
|
||||||
cancelImpl?()
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(messageId)
|
||||||
}))
|
if let previousId = previousId {
|
||||||
present(statusController, nil)
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(previousId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if controllerInteraction.currentMessageWithLoadingReplyThread == messageId {
|
||||||
|
controllerInteraction.currentMessageWithLoadingReplyThread = nil
|
||||||
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
|
||||||
let disposable = (foundIndex.get()
|
let progress = (progressSignal
|
||||||
|> take(1)
|
|> delay(0.15, queue: .mainQueue())).start()
|
||||||
|> deliverOnMainQueue).start(next: { [weak statusController] result in
|
|
||||||
statusController?.dismiss()
|
self.navigationActionDisposable.set((ChatControllerImpl.openMessageReplies(context: self.context, navigationController: navigationController, present: { [weak self] c, a in
|
||||||
|
self?.present(c, in: .window(.root), with: a)
|
||||||
|
}, messageId: messageId, isChannelPost: isChannelPost, atMessage: atMessageId, displayModalProgress: false)
|
||||||
|
|> afterDisposed {
|
||||||
|
progress.dispose()
|
||||||
|
}).start())
|
||||||
|
}
|
||||||
|
|
||||||
|
static func openMessageReplies(context: AccountContext, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?, displayModalProgress: Bool) -> Signal<Never, NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
if let result = result {
|
var cancelImpl: (() -> Void)?
|
||||||
let chatLocation: ChatLocation = .replyThread(threadMessageId: result.message.messageId, isChannelPost: result.isChannelPost, maxMessage: result.message.maxMessage, maxReadIncomingMessageId: result.message.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.message.maxReadOutgoingMessageId)
|
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
cancelImpl?()
|
||||||
|
}))
|
||||||
|
|
||||||
|
if displayModalProgress {
|
||||||
|
present(statusController, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let disposable = (fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId), atMessageId: atMessageId)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak statusController] result in
|
||||||
|
if displayModalProgress {
|
||||||
|
statusController?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
let chatLocation: ChatLocation = .replyThread(result.message)
|
||||||
|
|
||||||
let subject: ChatControllerSubject?
|
let subject: ChatControllerSubject?
|
||||||
if let atMessageId = atMessageId {
|
if let atMessageId = atMessageId {
|
||||||
@ -8279,13 +8391,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty, keepStack: .always))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty, keepStack: .always))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}, error: { _ in
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
cancelImpl = { [weak statusController] in
|
||||||
|
disposable.dispose()
|
||||||
|
statusController?.dismiss()
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
cancelImpl?()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
cancelImpl = { [weak statusController] in
|
|
||||||
disposable.dispose()
|
|
||||||
statusController?.dismiss()
|
|
||||||
}
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -130,6 +130,7 @@ public final class ChatControllerInteraction {
|
|||||||
var stickerSettings: ChatInterfaceStickerSettings
|
var stickerSettings: ChatInterfaceStickerSettings
|
||||||
var searchTextHighightState: (String, [MessageIndex])?
|
var searchTextHighightState: (String, [MessageIndex])?
|
||||||
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
||||||
|
var currentMessageWithLoadingReplyThread: MessageId?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool,
|
openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool,
|
||||||
|
@ -136,10 +136,10 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var addedThreadHead = false
|
var addedThreadHead = false
|
||||||
if case let .replyThread(messageId, isChannelPost, _, _, _) = location, view.earlierId == nil, !view.holeEarlier, !view.isLoading {
|
if case let .replyThread(replyThreadMessage) = location, view.earlierId == nil, !view.holeEarlier, !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 == replyThreadMessage.messageId:
|
||||||
if !messages.isEmpty {
|
if !messages.isEmpty {
|
||||||
let selection: ChatHistoryMessageSelection = .none
|
let selection: ChatHistoryMessageSelection = .none
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
|
|
||||||
let replyCount = view.entries.isEmpty ? 0 : 1
|
let replyCount = view.entries.isEmpty ? 0 : 1
|
||||||
|
|
||||||
entries.insert(.ReplyCountEntry(messages[0].index, isChannelPost, replyCount, presentationData), at: 1)
|
entries.insert(.ReplyCountEntry(messages[0].index, replyThreadMessage.isChannelPost, replyCount, presentationData), at: 1)
|
||||||
}
|
}
|
||||||
break loop
|
break loop
|
||||||
default:
|
default:
|
||||||
|
@ -627,15 +627,15 @@ 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(replyThreadMessage) = chatLocation {
|
||||||
additionalData.append(.cachedPeerData(messageId.peerId))
|
additionalData.append(.cachedPeerData(replyThreadMessage.messageId.peerId))
|
||||||
additionalData.append(.peerNotificationSettings(messageId.peerId))
|
additionalData.append(.peerNotificationSettings(replyThreadMessage.messageId.peerId))
|
||||||
if messageId.peerId.namespace == Namespaces.Peer.CloudChannel {
|
if replyThreadMessage.messageId.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: messageId.peerId)))
|
additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: replyThreadMessage.messageId.peerId)))
|
||||||
additionalData.append(.peer(messageId.peerId))
|
additionalData.append(.peer(replyThreadMessage.messageId.peerId))
|
||||||
}
|
}
|
||||||
|
|
||||||
additionalData.append(.message(messageId))
|
additionalData.append(.message(replyThreadMessage.messageId))
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentViewVersion = Atomic<Int?>(value: nil)
|
let currentViewVersion = Atomic<Int?>(value: nil)
|
||||||
@ -1173,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 let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.messageId == message.id {
|
||||||
isTopReplyThreadMessageShownValue = true
|
isTopReplyThreadMessageShownValue = true
|
||||||
}
|
}
|
||||||
case let .MessageGroupEntry(_, messages, _):
|
case let .MessageGroupEntry(_, messages, _):
|
||||||
@ -1203,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 let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.messageId == message.id {
|
||||||
isTopReplyThreadMessageShownValue = true
|
isTopReplyThreadMessageShownValue = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,44 +302,36 @@ enum ReplyThreadSubject {
|
|||||||
case groupMessage(MessageId)
|
case groupMessage(MessageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject) -> Signal<ReplyThreadInfo?, NoError> {
|
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject, atMessageId: MessageId?) -> Signal<ReplyThreadInfo, FetchChannelReplyThreadMessageError> {
|
||||||
let message: Signal<ChatReplyThreadMessage?, NoError>
|
let message: Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError>
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .channelPost(messageId):
|
case let .channelPost(messageId):
|
||||||
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId)
|
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId, atMessageId: atMessageId)
|
||||||
case let .groupMessage(messageId):
|
case let .groupMessage(messageId):
|
||||||
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId)
|
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId, atMessageId: atMessageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|> mapToSignal { message -> Signal<ReplyThreadInfo?, NoError> in
|
|> mapToSignal { replyThreadMessage -> Signal<ReplyThreadInfo, FetchChannelReplyThreadMessageError> in
|
||||||
guard let message = message else {
|
|
||||||
return .single(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let isChannelPost: Bool
|
|
||||||
switch subject {
|
|
||||||
case .channelPost:
|
|
||||||
isChannelPost = true
|
|
||||||
case .groupMessage:
|
|
||||||
isChannelPost = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||||
|
|
||||||
let preloadSignal = preloadedChatHistoryViewForLocation(
|
let input: ChatHistoryLocationInput
|
||||||
ChatHistoryLocationInput(
|
if let atMessageId = atMessageId {
|
||||||
content: .Initial(count: 60),
|
input = ChatHistoryLocationInput(
|
||||||
|
content: .InitialSearch(location: .id(atMessageId), count: 30),
|
||||||
id: 0
|
id: 0
|
||||||
),
|
)
|
||||||
|
} else {
|
||||||
|
input = ChatHistoryLocationInput(
|
||||||
|
content: .Initial(count: 30),
|
||||||
|
id: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let preloadSignal = preloadedChatHistoryViewForLocation(
|
||||||
|
input,
|
||||||
context: context,
|
context: context,
|
||||||
chatLocation: .replyThread(
|
chatLocation: .replyThread(replyThreadMessage),
|
||||||
threadMessageId: message.messageId,
|
|
||||||
isChannelPost: isChannelPost,
|
|
||||||
maxMessage: message.maxMessage,
|
|
||||||
maxReadIncomingMessageId: message.maxReadIncomingMessageId,
|
|
||||||
maxReadOutgoingMessageId: message.maxReadOutgoingMessageId
|
|
||||||
),
|
|
||||||
chatLocationContextHolder: chatLocationContextHolder,
|
chatLocationContextHolder: chatLocationContextHolder,
|
||||||
fixedCombinedReadStates: nil,
|
fixedCombinedReadStates: nil,
|
||||||
tagMask: nil,
|
tagMask: nil,
|
||||||
@ -362,13 +354,14 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { isEmpty -> ReplyThreadInfo? in
|
|> map { isEmpty -> ReplyThreadInfo in
|
||||||
return ReplyThreadInfo(
|
return ReplyThreadInfo(
|
||||||
message: message,
|
message: replyThreadMessage,
|
||||||
isChannelPost: isChannelPost,
|
isChannelPost: replyThreadMessage.isChannelPost,
|
||||||
isEmpty: isEmpty,
|
isEmpty: isEmpty,
|
||||||
contextHolder: chatLocationContextHolder
|
contextHolder: chatLocationContextHolder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,8 +468,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isReplyThreadHead = false
|
var isReplyThreadHead = false
|
||||||
if case let .replyThread(messageId, _, _, _, _) = chatPresentationInterfaceState.chatLocation {
|
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||||
isReplyThreadHead = messages[0].id == messageId
|
isReplyThreadHead = messages[0].id == replyThreadMessage.messageId
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isReplyThreadHead, data.canReply {
|
if !isReplyThreadHead, data.canReply {
|
||||||
@ -614,8 +614,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
|||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { c, _ in
|
}, action: { c, _ in
|
||||||
let foundIndex = Promise<ChatReplyThreadMessage?>()
|
let foundIndex = Promise<ChatReplyThreadMessage?>()
|
||||||
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel {
|
||||||
foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id))
|
foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id, atMessageId: nil)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
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 {
|
||||||
@ -745,8 +749,8 @@ 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(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||||
threadMessageId = replyThread
|
threadMessageId = replyThreadMessage.messageId
|
||||||
}
|
}
|
||||||
let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|
let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|
||||||
|> map { result -> String? in
|
|> map { result -> String? in
|
||||||
|
@ -561,15 +561,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
} else if incoming {
|
} else if incoming {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
}
|
||||||
case let .replyThread(messageId, isChannelPost, _, _, _):
|
case let .replyThread(replyThreadMessage):
|
||||||
if messageId.peerId != item.context.account.peerId {
|
if replyThreadMessage.messageId.peerId != item.context.account.peerId {
|
||||||
if messageId.peerId.isGroupOrChannel && item.message.author != nil {
|
if replyThreadMessage.messageId.peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isChannelPost, messageId == item.message.id {
|
if replyThreadMessage.isChannelPost, replyThreadMessage.messageId == item.message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,7 +676,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
edited = true
|
edited = true
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
@ -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(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == 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))
|
||||||
}
|
}
|
||||||
@ -1359,6 +1359,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let selectionState = item.controllerInteraction.selectionState {
|
if let selectionState = item.controllerInteraction.selectionState {
|
||||||
var selected = false
|
var selected = false
|
||||||
var incoming = true
|
var incoming = true
|
||||||
|
@ -271,7 +271,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
|
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
|
||||||
let currentImage = self.media as? TelegramMediaImage
|
let currentImage = self.media as? TelegramMediaImage
|
||||||
let imageLayout = self.inlineImageNode.asyncLayout()
|
let imageLayout = self.inlineImageNode.asyncLayout()
|
||||||
@ -284,7 +284,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
||||||
|
|
||||||
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in
|
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in
|
||||||
let isPreview = presentationData.isPreview
|
let isPreview = presentationData.isPreview
|
||||||
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = chatLocation {
|
||||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
@ -424,7 +424,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else if file.isInstantVideo {
|
} else if file.isInstantVideo {
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
||||||
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData, attributes: attributes), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
|
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
|
||||||
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
|
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
|
||||||
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
||||||
} else if file.isVideo {
|
} else if file.isVideo {
|
||||||
@ -474,7 +474,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, refineLayout) = contentFileLayout(context, presentationData, message, attributes, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
let (_, refineLayout) = contentFileLayout(context, presentationData, message, chatLocation, attributes, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
||||||
refineContentFileLayout = refineLayout
|
refineContentFileLayout = refineLayout
|
||||||
}
|
}
|
||||||
} else if let image = media as? TelegramMediaImage {
|
} else if let image = media as? TelegramMediaImage {
|
||||||
|
@ -98,15 +98,17 @@ final class ChatMessageBubbleContentItem {
|
|||||||
let controllerInteraction: ChatControllerInteraction
|
let controllerInteraction: ChatControllerInteraction
|
||||||
let message: Message
|
let message: Message
|
||||||
let read: Bool
|
let read: Bool
|
||||||
|
let chatLocation: ChatLocation
|
||||||
let presentationData: ChatPresentationData
|
let presentationData: ChatPresentationData
|
||||||
let associatedData: ChatMessageItemAssociatedData
|
let associatedData: ChatMessageItemAssociatedData
|
||||||
let attributes: ChatMessageEntryAttributes
|
let attributes: ChatMessageEntryAttributes
|
||||||
|
|
||||||
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes) {
|
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.message = message
|
self.message = message
|
||||||
self.read = read
|
self.read = read
|
||||||
|
self.chatLocation = chatLocation
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.associatedData = associatedData
|
self.associatedData = associatedData
|
||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
|
@ -837,13 +837,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
var hasAvatar = false
|
var hasAvatar = false
|
||||||
|
|
||||||
var allowFullWidth = false
|
var allowFullWidth = false
|
||||||
let chatLocationPeerId: PeerId
|
let chatLocationPeerId: PeerId = item.chatLocation.peerId
|
||||||
switch item.chatLocation {
|
|
||||||
case let .peer(peerId):
|
|
||||||
chatLocationPeerId = peerId
|
|
||||||
case let .replyThread(messageId, _, _, _, _):
|
|
||||||
chatLocationPeerId = messageId.peerId
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let peerId = chatLocationPeerId
|
let peerId = chatLocationPeerId
|
||||||
@ -885,7 +879,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(replyThreadMessage) = item.chatLocation, replyThreadMessage.isChannelPost, replyThreadMessage.messageId == firstMessage.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -912,7 +906,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
|
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
|
||||||
|
|
||||||
var needShareButton = false
|
var needShareButton = false
|
||||||
if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id {
|
||||||
|
needShareButton = false
|
||||||
|
allowFullWidth = true
|
||||||
|
} else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
|
||||||
needShareButton = false
|
needShareButton = false
|
||||||
} else if item.message.id.peerId == item.context.account.peerId {
|
} else if item.message.id.peerId == item.context.account.peerId {
|
||||||
if let _ = sourceReference {
|
if let _ = sourceReference {
|
||||||
@ -1048,7 +1045,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(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == attribute.messageId {
|
||||||
} else {
|
} else {
|
||||||
replyMessage = firstMessage.associatedMessages[attribute.messageId]
|
replyMessage = firstMessage.associatedMessages[attribute.messageId]
|
||||||
}
|
}
|
||||||
@ -1140,7 +1137,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
|
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes)
|
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes)
|
||||||
|
|
||||||
var itemSelection: Bool?
|
var itemSelection: Bool?
|
||||||
if case .mosaic = prepareContentPosition {
|
if case .mosaic = prepareContentPosition {
|
||||||
@ -1294,7 +1291,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
@ -2953,6 +2950,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id {
|
||||||
|
canHaveSelection = false
|
||||||
|
}
|
||||||
|
|
||||||
if let selectionState = item.controllerInteraction.selectionState, canHaveSelection {
|
if let selectionState = item.controllerInteraction.selectionState, canHaveSelection {
|
||||||
var selected = false
|
var selected = false
|
||||||
|
@ -7,6 +7,7 @@ import SwiftSignalKit
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SyncCore
|
import SyncCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import RadialStatusNode
|
||||||
|
|
||||||
final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
|
final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
@ -17,6 +18,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
|
|||||||
private let buttonNode: HighlightTrackingButtonNode
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
private let avatarsNode: MergedAvatarsNode
|
private let avatarsNode: MergedAvatarsNode
|
||||||
private let unreadIconNode: ASImageNode
|
private let unreadIconNode: ASImageNode
|
||||||
|
private var statusNode: RadialStatusNode?
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
@ -257,6 +259,32 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
strongSelf.iconNode.isHidden = !replyPeers.isEmpty
|
strongSelf.iconNode.isHidden = !replyPeers.isEmpty
|
||||||
|
|
||||||
|
let hasActivity = item.controllerInteraction.currentMessageWithLoadingReplyThread == item.message.id
|
||||||
|
|
||||||
|
if hasActivity {
|
||||||
|
strongSelf.arrowNode.isHidden = true
|
||||||
|
let statusNode: RadialStatusNode
|
||||||
|
if let current = strongSelf.statusNode {
|
||||||
|
statusNode = current
|
||||||
|
} else {
|
||||||
|
statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
|
strongSelf.statusNode = statusNode
|
||||||
|
strongSelf.buttonNode.addSubnode(statusNode)
|
||||||
|
}
|
||||||
|
let statusSize = CGSize(width: 20.0, height: 20.0)
|
||||||
|
statusNode.frame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width - 11.0, y: 8.0 + topOffset), size: statusSize)
|
||||||
|
statusNode.transitionToState(.progress(color: messageTheme.accentTextColor, lineWidth: 1.5, value: nil, cancelEnabled: false), animated: false, synchronous: false, completion: {})
|
||||||
|
} else {
|
||||||
|
strongSelf.arrowNode.isHidden = false
|
||||||
|
if let statusNode = strongSelf.statusNode {
|
||||||
|
strongSelf.statusNode = nil
|
||||||
|
statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak statusNode] _ in
|
||||||
|
statusNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
strongSelf.arrowNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: 3.0 + topOffset), size: CGSize(width: imageSize * 3.0, height: imageSize))
|
let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: 3.0 + topOffset), size: CGSize(width: imageSize * 3.0, height: imageSize))
|
||||||
strongSelf.avatarsNode.frame = avatarsFrame
|
strongSelf.avatarsNode.frame = avatarsFrame
|
||||||
strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size)
|
strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size)
|
||||||
|
@ -152,7 +152,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
|||||||
let text: String = item.message.text
|
let text: String = item.message.text
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
||||||
|
|
||||||
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.attributes, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.chatLocation, item.attributes, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -182,8 +182,8 @@ 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(replyThreadMessage):
|
||||||
messagePeerId = messageId.peerId
|
messagePeerId = replyThreadMessage.messageId.peerId
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -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(replyThreadMessage) = item.chatLocation, replyThreadMessage.isChannelPost, replyThreadMessage.messageId == item.message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
|
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
|
||||||
|
|
||||||
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
||||||
|
|
||||||
@ -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(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == 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))
|
||||||
}
|
}
|
||||||
@ -854,6 +854,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let selectionState = item.controllerInteraction.selectionState {
|
if let selectionState = item.controllerInteraction.selectionState {
|
||||||
var selected = false
|
var selected = false
|
||||||
var incoming = true
|
var incoming = true
|
||||||
|
@ -204,7 +204,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
|
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
|
||||||
let currentFile = self.file
|
let currentFile = self.file
|
||||||
|
|
||||||
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
@ -214,7 +214,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentMessage = self.message
|
let currentMessage = self.message
|
||||||
|
|
||||||
return { context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
|
return { context, presentationData, message, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
|
||||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||||
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
||||||
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
||||||
@ -300,7 +300,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = chatLocation {
|
||||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
@ -971,12 +971,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
|
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
|
||||||
let currentAsyncLayout = node?.asyncLayout()
|
let currentAsyncLayout = node?.asyncLayout()
|
||||||
|
|
||||||
return { context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
|
return { context, presentationData, message, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
|
||||||
var fileNode: ChatMessageInteractiveFileNode
|
var fileNode: ChatMessageInteractiveFileNode
|
||||||
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
|
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
|
||||||
|
|
||||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||||
fileNode = node
|
fileNode = node
|
||||||
@ -986,7 +986,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
fileLayout = fileNode.asyncLayout()
|
fileLayout = fileNode.asyncLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize)
|
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize)
|
||||||
|
|
||||||
return (initialWidth, { constrainedSize in
|
return (initialWidth, { constrainedSize in
|
||||||
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
||||||
|
@ -257,7 +257,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -276,13 +276,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
|||||||
var effectiveAuthor: Peer?
|
var effectiveAuthor: Peer?
|
||||||
let displayAuthorInfo: Bool
|
let displayAuthorInfo: Bool
|
||||||
|
|
||||||
let messagePeerId: PeerId
|
let messagePeerId: PeerId = chatLocation.peerId
|
||||||
switch chatLocation {
|
|
||||||
case let .peer(peerId):
|
|
||||||
messagePeerId = peerId
|
|
||||||
case let .replyThread(messageId, _, _, _, _):
|
|
||||||
messagePeerId = messageId.peerId
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let peerId = messagePeerId
|
let peerId = messagePeerId
|
||||||
@ -333,7 +327,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(replyThreadMessage) = chatLocation, replyThreadMessage.isChannelPost, replyThreadMessage.messageId == message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
if !hasActionMedia && !isBroadcastChannel {
|
if !hasActionMedia && !isBroadcastChannel {
|
||||||
|
@ -182,7 +182,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -1030,7 +1030,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? RestrictedContentMessageAttribute {
|
} else if let attribute = attribute as? RestrictedContentMessageAttribute {
|
||||||
rawText = attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) ?? ""
|
rawText = attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) ?? ""
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -249,15 +249,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
} else if incoming {
|
} else if incoming {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
}
|
||||||
case let .replyThread(messageId, isChannelPost, _, _, _):
|
case let .replyThread(replyThreadMessage):
|
||||||
if messageId.peerId != item.context.account.peerId {
|
if replyThreadMessage.messageId.peerId != item.context.account.peerId {
|
||||||
if messageId.peerId.isGroupOrChannel && item.message.author != nil {
|
if replyThreadMessage.messageId.peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isChannelPost, messageId == item.message.id {
|
if replyThreadMessage.isChannelPost, replyThreadMessage.messageId == item.message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
edited = true
|
edited = true
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
@ -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(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == 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))
|
||||||
}
|
}
|
||||||
@ -920,6 +920,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
|
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||||
var isEmoji = false
|
var isEmoji = false
|
||||||
if let item = self.item, item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) {
|
if let item = self.item, item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) {
|
||||||
|
@ -115,7 +115,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute {
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -121,6 +121,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
|
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
|
||||||
let openPeersNearby: () -> Void
|
let openPeersNearby: () -> Void
|
||||||
let unarchivePeer: () -> Void
|
let unarchivePeer: () -> Void
|
||||||
|
let scrollToTop: () -> Void
|
||||||
let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void
|
let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void
|
||||||
let statuses: ChatPanelInterfaceInteractionStatuses?
|
let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||||
|
|
||||||
@ -196,6 +197,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
openPeersNearby: @escaping () -> Void,
|
openPeersNearby: @escaping () -> Void,
|
||||||
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
|
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
|
||||||
unarchivePeer: @escaping () -> Void,
|
unarchivePeer: @escaping () -> Void,
|
||||||
|
scrollToTop: @escaping () -> Void,
|
||||||
viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void,
|
viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void,
|
||||||
statuses: ChatPanelInterfaceInteractionStatuses?
|
statuses: ChatPanelInterfaceInteractionStatuses?
|
||||||
) {
|
) {
|
||||||
@ -270,6 +272,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
self.openPeersNearby = openPeersNearby
|
self.openPeersNearby = openPeersNearby
|
||||||
self.displaySearchResultsTooltip = displaySearchResultsTooltip
|
self.displaySearchResultsTooltip = displaySearchResultsTooltip
|
||||||
self.unarchivePeer = unarchivePeer
|
self.unarchivePeer = unarchivePeer
|
||||||
|
self.scrollToTop = scrollToTop
|
||||||
self.viewReplies = viewReplies
|
self.viewReplies = viewReplies
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
}
|
}
|
||||||
|
@ -299,11 +299,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
@objc func tapped() {
|
@objc func tapped() {
|
||||||
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
|
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
|
||||||
if self.isReplyThread {
|
if self.isReplyThread {
|
||||||
if let sourceReference = message.sourceReference {
|
interfaceInteraction.scrollToTop()
|
||||||
|
/*if let sourceReference = message.sourceReference {
|
||||||
interfaceInteraction.navigateToMessage(sourceReference.messageId, true)
|
interfaceInteraction.navigateToMessage(sourceReference.messageId, true)
|
||||||
} else {
|
} else {
|
||||||
interfaceInteraction.navigateToMessage(message.id, false)
|
interfaceInteraction.navigateToMessage(message.id, false)
|
||||||
}
|
}*/
|
||||||
} else {
|
} else {
|
||||||
interfaceInteraction.navigateToMessage(message.id, false)
|
interfaceInteraction.navigateToMessage(message.id, false)
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,9 @@ final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
}, openScheduledMessages: {
|
}, openScheduledMessages: {
|
||||||
}, openPeersNearby: {
|
}, openPeersNearby: {
|
||||||
}, displaySearchResultsTooltip: { _, _ in
|
}, displaySearchResultsTooltip: { _, _ in
|
||||||
}, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil)
|
}, unarchivePeer: {
|
||||||
|
}, scrollToTop: {
|
||||||
|
}, viewReplies: { _, _ in }, statuses: nil)
|
||||||
|
|
||||||
self.navigationItem.titleView = self.titleView
|
self.navigationItem.titleView = self.titleView
|
||||||
|
|
||||||
|
@ -817,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, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
|
case let .replyThreadMessage(replyThreadMessage, 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, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId), subject: .message(messageId)))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(messageId)))
|
||||||
}
|
}
|
||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
let packReference: StickerPackReference = .name(name)
|
let packReference: StickerPackReference = .name(name)
|
||||||
|
@ -190,7 +190,6 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if case .replyThread = interfaceState.chatLocation {
|
if case .replyThread = interfaceState.chatLocation {
|
||||||
self.calendarButton.isHidden = true
|
|
||||||
canSearchMembers = false
|
canSearchMembers = false
|
||||||
}
|
}
|
||||||
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
|
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
|
||||||
|
@ -792,8 +792,8 @@ 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(replyThreadMessage) = interfaceState.chatLocation {
|
||||||
if isChannelPost {
|
if replyThreadMessage.isChannelPost {
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment
|
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment
|
||||||
} else {
|
} else {
|
||||||
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderReply
|
placeholder = interfaceState.strings.Conversation_InputTextPlaceholderReply
|
||||||
|
@ -125,8 +125,8 @@ 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(replyThreadMessage):
|
||||||
if message.id.peerId == messageId.peerId {
|
if message.id.peerId == replyThreadMessage.messageId.peerId {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,11 +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, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
|
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
|
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in
|
||||||
present(c, a)
|
present(c, a)
|
||||||
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
|
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start()
|
||||||
}
|
}
|
||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
dismissInput()
|
dismissInput()
|
||||||
|
@ -431,7 +431,9 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
}, openScheduledMessages: {
|
}, openScheduledMessages: {
|
||||||
}, openPeersNearby: {
|
}, openPeersNearby: {
|
||||||
}, displaySearchResultsTooltip: { _, _ in
|
}, displaySearchResultsTooltip: { _, _ in
|
||||||
}, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil)
|
}, unarchivePeer: {
|
||||||
|
}, scrollToTop: {
|
||||||
|
}, viewReplies: { _, _ in }, statuses: nil)
|
||||||
|
|
||||||
self.selectionPanel.interfaceInteraction = interfaceInteraction
|
self.selectionPanel.interfaceInteraction = interfaceInteraction
|
||||||
|
|
||||||
|
@ -59,11 +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, maxReadIncomingMessageId, maxReadOutgoingMessageId, messageId):
|
case let .replyThreadMessage(replyThreadMessage, messageId):
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { [weak controller] c, a in
|
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { [weak controller] c, a in
|
||||||
controller?.present(c, in: .window(.root), with: a)
|
controller?.present(c, in: .window(.root), with: a)
|
||||||
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
|
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start()
|
||||||
}
|
}
|
||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
let packReference: StickerPackReference = .name(name)
|
let packReference: StickerPackReference = .name(name)
|
||||||
|
@ -321,12 +321,16 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
|||||||
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)))
|
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)))
|
||||||
case let .replyThread(id, replyId):
|
case let .replyThread(id, replyId):
|
||||||
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
|
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
|
||||||
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId)
|
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId, atMessageId: nil)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|> map { result -> ResolvedUrl? in
|
|> map { result -> ResolvedUrl? in
|
||||||
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, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.maxReadIncomingMessageId, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
|
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -367,12 +371,16 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
|||||||
if let foundPeer = foundPeer {
|
if let foundPeer = foundPeer {
|
||||||
if let threadId = threadId {
|
if let threadId = threadId {
|
||||||
let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId)
|
let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId)
|
||||||
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId)
|
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId, atMessageId: nil)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|> map { result -> ResolvedUrl? in
|
|> map { result -> ResolvedUrl? in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId)
|
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)
|
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)))
|
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)))
|
||||||
|
2
third-party/webrtc/webrtc-ios
vendored
2
third-party/webrtc/webrtc-ios
vendored
@ -1 +1 @@
|
|||||||
Subproject commit e4d49e73cd8206518e7b3dd75d54af0f0ef5b810
|
Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877
|
Loading…
x
Reference in New Issue
Block a user