diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index de3a31a0bd..f0689eb51a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -170,7 +170,7 @@ public enum ResolvedUrl { case botStart(peerId: PeerId, payload: String) case groupBotStart(peerId: PeerId, payload: String) 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 instantView(TelegramMediaWebpage, String?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) @@ -253,7 +253,7 @@ public enum ChatSearchDomain: Equatable { public enum ChatLocation: Equatable { case peer(PeerId) - case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) + case replyThread(ChatReplyThreadMessage) } public final class NavigateToChatControllerParams { diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index 059f974907..cbe6a136bd 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -230,7 +230,7 @@ static int32_t fixedTimeDifferenceValue = 0; #if DEBUG _tempKeyExpiration = 1 * 60 * 60; #else - _tempKeyExpiration = 1 * 60 * 60; + _tempKeyExpiration = 24 * 60 * 60; #endif _datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init]; diff --git a/submodules/Postbox/Sources/ChatLocation.swift b/submodules/Postbox/Sources/ChatLocation.swift index bdf23981a7..cf24d9d842 100644 --- a/submodules/Postbox/Sources/ChatLocation.swift +++ b/submodules/Postbox/Sources/ChatLocation.swift @@ -6,7 +6,7 @@ public enum ChatLocationInput { case external(PeerId, Signal) } -enum ResolvedChatLocationInput { +public enum ResolvedChatLocationInput { case peer(PeerId) case external(MessageHistoryViewExternalInput) } diff --git a/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift index dabf022f6d..d973c85cb8 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift @@ -80,7 +80,7 @@ final class MessageHistoryThreadHoleIndexTable: Table { let key = ValueBoxKey(length: 8 + 8 + 4 + 4) key.setInt64(0, value: peerId.toInt64()) key.setInt64(8, value: threadId) - key.setInt32(8 + 4, value: namespace) + key.setInt32(8 + 8, value: namespace) let tagValue: UInt32 switch space { case .everywhere: diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index b1c6eb8377..5e81d19360 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -68,6 +68,11 @@ public final class Transaction { 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) { assert(!self.disposed) 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) } + 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) { assert(!self.disposed) 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, 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, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] var mainPeerIdForTopTaggedMessages: PeerId? switch peerIds { diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift index 9a7f4ea544..05b3a60da2 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -38,6 +38,7 @@ public func openUserGeneratedUrl(context: AccountContext, url: String, concealed } } |> deliverOnMainQueue).start(next: { result in + progressDisposable.dispose() openResolved(result) })) } diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index 3b2a39acb2..aa247bbdcc 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -2291,21 +2291,21 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP final class MessageThreadStatsRecord { var count: Int = 0 - var peers: [PeerId] = [] + var peers: [ReplyThreadUserMessage] = [] } 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] { value.count += add - remove - if let addedMessagePeer = addedMessagePeer { - value.peers.append(addedMessagePeer) + if let addedMessagePeer = addedMessagePeer, let addedMessageId = addedMessageId { + value.peers.append(ReplyThreadUserMessage(id: addedMessagePeer, messageId: addedMessageId, isOutgoing: isOutgoing)) } } else { let value = MessageThreadStatsRecord() messageThreadStatsDifferences[threadMessageId] = value value.count = add - remove - if let addedMessagePeer = addedMessagePeer { - value.peers.append(addedMessagePeer) + if let addedMessagePeer = addedMessagePeer, let addedMessageId = addedMessageId { + 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) if id.peerId.namespace == Namespaces.Peer.CloudChannel { 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 { 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 { @@ -2435,7 +2442,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } case let .DeleteMessages(ids): 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): if let message = transaction.getMessage(id) { diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index fc47e0f5c0..126b543857 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -241,8 +241,15 @@ private final class FeaturedStickerPacksContext { } private struct ViewCountContextState { + struct ReplyInfo { + var commentsPeerId: PeerId? + var maxReadIncomingMessageId: MessageId? + var maxMessageId: MessageId? + } + var timestamp: Int32 var clientId: Int32 + var result: ReplyInfo? func isStillValidFor(_ other: ViewCountContextState) -> Bool { 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 { + 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, clientId: Int32) { self.queue.async { 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 { let messageTimestamp = self.updatedViewCountMessageIdsAndTimestamps[messageId] if messageTimestamp == nil || !messageTimestamp!.isStillValidFor(updatedState) { @@ -609,106 +648,128 @@ public final class AccountViewTracker { self.nextUpdatedViewCountDisposableId += 1 if let account = self.account { - let signal = (account.postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { - return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal 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 { + let signal: Signal<[MessageId: ViewCountContextState], NoError> = (account.postbox.transaction { transaction -> Signal<[MessageId: ViewCountContextState], NoError> in + guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else { 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 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 self?.queue.async { 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 { + public func polledChannel(peerId: PeerId) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.queue.async { diff --git a/submodules/TelegramCore/Sources/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiGroupOrChannel.swift index d5898d42f8..3b1d5175d9 100644 --- a/submodules/TelegramCore/Sources/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiGroupOrChannel.swift @@ -55,7 +55,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { 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) 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 if (flags & Int32(1 << 1)) != 0 { @@ -130,7 +130,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { switch rhs { case .chat, .chatEmpty, .chatForbidden, .channelForbidden: 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 if accessHash != nil && !isMin { return parseTelegramGroupOrChannel(chat: rhs) @@ -149,9 +149,45 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { let infoFlags = TelegramChannelGroupFlags() 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)) } else { 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) +} + diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index 821b24842e..08302c906c 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -234,7 +234,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes if let threadId = updatedMessage.threadId { let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId) 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)]) } } } diff --git a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift index 5c3cbde19c..569571ae66 100644 --- a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift @@ -102,7 +102,7 @@ private final class HistoryPreloadEntry: Comparable { |> mapToSignal { download -> Signal in switch hole.hole { case let .peer(peerHole): - return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, 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 } } diff --git a/submodules/TelegramCore/Sources/Holes.swift b/submodules/TelegramCore/Sources/Holes.swift index 91175c190e..5d4e5e1799 100644 --- a/submodules/TelegramCore/Sources/Holes.swift +++ b/submodules/TelegramCore/Sources/Holes.swift @@ -125,7 +125,39 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis |> switchToLatest } -func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal { +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 { let count = min(100, rawCount) return postbox.stateView() @@ -137,389 +169,377 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } |> take(1) - |> mapToSignal { _ -> Signal in - return postbox.transaction { transaction -> (Peer?, Int32) in - var hash: Int32 = 0 - switch space { - case .everywhere: - if let threadId = threadId { - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - } 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 + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction -> (Api.InputPeer?, Int32) in + switch peerInput { + case let .direct(peerId, _): + return (transaction.getPeer(peerId).flatMap(forceApiInputPeer), 0) + case let .threadFromChannel(channelMessageId): + return (transaction.getPeer(channelMessageId.peerId).flatMap(forceApiInputPeer), 0) + } + } + |> mapToSignal { (inputPeer, hash) -> Signal in + guard let inputPeer = inputPeer else { + return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet())) } - return (transaction.getPeer(peerId), hash) - } - |> mapToSignal { (peer, hash) -> Signal in - guard let peer = peer else { - return .single(IndexSet()) - } - if let inputPeer = forceApiInputPeer(peer) { - print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") - Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") - let request: Signal - var implicitelyFillHole = false - let minMaxRange: ClosedRange - - switch space { - case .everywhere: - if let threadId = threadId { - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - - let rangeStartId = start.id - let rangeEndId = min(end.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId - } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() - } + print("fetchMessageHistoryHole for \(peerInput) direction \(direction) space \(space)") + Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peerInput) direction \(direction) space \(space)") + let request: Signal + var implicitelyFillHole = false + let minMaxRange: ClosedRange + + switch space { + case .everywhere: + if let requestThreadId = peerInput.requestThreadId { + 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 { - 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() - } + 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.getReplies(peer: inputPeer, msgId: threadId.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 { - 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() - } + 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 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 - 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 - } else { - 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) - } - } + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + + minMaxRange = 1 ... (Int32.max - 1) } + request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: 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 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 + 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 { - 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 { transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) } - - updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in - return updated - }) - updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - - print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) threadId: \(String(describing: threadId)) done") - - return IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)) + case .threadFromChannel: + break + } + + updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in + return updated }) - } - } else { - return .complete() + updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) + + 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 { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence + if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } } } diff --git a/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift index e3d2082713..e448f7c71f 100644 --- a/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/ManagedMessageHistoryHoles.swift @@ -55,7 +55,7 @@ func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox for (entry, disposable) in added { switch entry.hole { case let .peer(hole): - disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, 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()) } } }) diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift index 615c87a202..d003760971 100644 --- a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -4,6 +4,14 @@ import Postbox import SwiftSignalKit 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 let queue: Queue private let account: Account @@ -41,37 +49,34 @@ private class ReplyThreadHistoryContextImpl { private var initialStateDisposable: Disposable? private var holesDisposable: Disposable? private var readStateDisposable: Disposable? + private var updateInitialStateDisposable: Disposable? 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.account = account - self.messageId = messageId + self.messageId = data.messageId - self.maxReadOutgoingMessageIdValue = maxReadOutgoingMessageId + self.maxReadOutgoingMessageIdValue = data.maxReadOutgoingMessageId self.maxReadOutgoingMessageId.set(.single(self.maxReadOutgoingMessageIdValue)) self.initialStateDisposable = (account.postbox.transaction { transaction -> State in - var indices = transaction.getThreadIndexHoles(peerId: messageId.peerId, threadId: makeMessageThreadId(messageId), namespace: Namespaces.Message.Cloud) - switch maxMessage { - case .unknown: - indices.insert(integersIn: 1 ..< Int(Int32.max - 1)) - case let .known(maxMessageId): - indices.insert(integersIn: 1 ..< Int(Int32.max - 1)) - /*if let maxMessageId = maxMessageId { - 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() - }*/ + var indices = transaction.getThreadIndexHoles(peerId: data.messageId.peerId, threadId: makeMessageThreadId(data.messageId), namespace: Namespaces.Message.Cloud) + indices.subtract(data.initialFilledHoles) + + let isParticipant = transaction.getPeerChatListIndex(data.messageId.peerId) != nil + if isParticipant { + let historyHoles = transaction.getHoles(peerId: data.messageId.peerId, namespace: Namespaces.Message.Cloud) + indices.formIntersection(historyHoles) } - 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 guard let strongSelf = self else { @@ -108,8 +113,102 @@ private class ReplyThreadHistoryContextImpl { guard let strongSelf = self else { return } - if let value = outgoing[messageId] { - strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud, id: value) + if let value = outgoing[data.messageId] { + strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: data.messageId.peerId, namespace: Namespaces.Message.Cloud, id: value) + } + }) + + let updateInitialState: Signal = account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(data.messageId.peerId).flatMap(apiInputPeer) + } + |> castError(FetchChannelReplyThreadMessageError.self) + |> mapToSignal { inputPeer -> Signal 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 in + return account.postbox.transaction { transaction -> Signal 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.holesDisposable?.dispose() self.readDisposable.dispose() + self.updateInitialStateDisposable?.dispose() } func setCurrentHole(entry: MessageHistoryHolesViewEntry?) { @@ -129,7 +229,7 @@ private class ReplyThreadHistoryContextImpl { return } if var currentHoles = strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] { - currentHoles.subtract(removedHoleIndices) + currentHoles.subtract(removedHoleIndices.removedIndices) strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] = currentHoles } })) @@ -139,11 +239,11 @@ private class ReplyThreadHistoryContextImpl { } } - private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal { + private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal { switch entry.hole { case let .peer(hole): let fetchCount = min(entry.count, 100) - return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount) + 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 { if let attribute = attribute as? SourceReferenceMessageAttribute { if let sourceMessage = transaction.getMessage(attribute.messageId) { + account.viewTracker.applyMaxReadIncomingMessageIdForReplyInfo(id: attribute.messageId, maxReadIncomingMessageId: messageIndex.id) + var updatedAttribute: ReplyThreadMessageAttribute? for i in 0 ..< sourceMessage.attributes.count { 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 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 enum MaxMessage: Equatable { - case unknown - case known(MessageId?) - } - +public struct ChatReplyThreadMessage: Equatable { public var messageId: MessageId - public var maxMessage: MaxMessage + public var isChannelPost: Bool + public var maxMessage: MessageId? public var maxReadIncomingMessageId: 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.isChannelPost = isChannelPost self.maxMessage = maxMessage self.maxReadIncomingMessageId = maxReadIncomingMessageId self.maxReadOutgoingMessageId = maxReadOutgoingMessageId + self.initialFilledHoles = initialFilledHoles } } -public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal { +public enum FetchChannelReplyThreadMessageError { + case generic +} + +public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId, atMessageId: MessageId?) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) } - |> mapToSignal { inputPeer -> Signal in + |> castError(FetchChannelReplyThreadMessageError.self) + |> mapToSignal { inputPeer -> Signal in guard let inputPeer = inputPeer else { - return .single(nil) + return .fail(.generic) } - let discussionMessage: Signal = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) + + let replyInfo = Promise() + replyInfo.set(account.viewTracker.replyInfoForMessageId(messageId)) + + let remoteDiscussionMessageSignal: Signal = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - - return discussionMessage - |> mapToSignal { discussionMessage -> Signal in + |> mapToSignal { discussionMessage -> Signal in guard let discussionMessage = discussionMessage else { return .single(nil) } - return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in + return account.postbox.transaction { transaction -> DiscussionMessage? in switch discussionMessage { case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users): 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) - let resolvedMaxMessage: ChatReplyThreadMessage.MaxMessage + let resolvedMaxMessage: MessageId? if let maxId = maxId { - resolvedMaxMessage = .known(MessageId( + resolvedMaxMessage = MessageId( peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: maxId - )) + ) } else { - resolvedMaxMessage = .known(nil) + resolvedMaxMessage = nil } - return ChatReplyThreadMessage( + return DiscussionMessage( messageId: parsedIndex.id, + isChannelPost: true, maxMessage: resolvedMaxMessage, maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in 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 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 in + if let result = result { + return .single(result) + } else { + return remoteDiscussionMessageSignal + } + } + let discussionMessage = Promise() + 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 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 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 in + guard let discussionMessage = discussionMessage else { + return .fail(.generic) + } + return account.postbox.transaction { transaction -> Signal 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 + } } } diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index ce26042057..69a70251ce 100644 --- a/submodules/TelegramCore/Sources/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/SearchMessages.swift @@ -539,14 +539,13 @@ func fetchRemoteMessage(postbox: Postbox, source: FetchMessageHistoryHoleSource, } } -public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timestamp: Int32) -> Signal { +public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, threadId: Int64?, timestamp: Int32) -> Signal { return account.postbox.transaction { transaction -> Signal in if peerId.namespace == Namespaces.Peer.SecretChat { return .single(transaction.findClosestMessageIdByTimestamp(peerId: peerId, timestamp: timestamp)) } else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { - var secondaryIndex: Signal = .single(nil) - 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)) + if let threadId = threadId { + 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)) |> map { result -> MessageIndex? in let messages: [Api.Message] switch result { @@ -569,45 +568,76 @@ public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timesta |> `catch` { _ -> Signal in return .single(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 - 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 = [] + return primaryIndex + |> map { primaryIndex -> MessageId? in + return primaryIndex?.id } - for message in messages { - if let message = StoreMessage(apiMessage: message) { - return message.index + } else { + var secondaryIndex: Signal = .single(nil) + 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 in + return .single(nil) } } - return nil - } - |> `catch` { _ -> Signal 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 + 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 + 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 + } } - } else if let primaryIndex = primaryIndex { - return primaryIndex.id - } else if let secondaryIndex = secondaryIndex { - return secondaryIndex.id - } else { return nil } + |> `catch` { _ -> Signal 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 { return .single(nil) @@ -626,19 +656,26 @@ public func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerRefe return .generic } |> mapToSignal { result -> Signal in - if let updatedPeer = result.first.flatMap(TelegramUser.init(user:)), updatedPeer.id == peer.id { - return postbox.transaction { transaction -> Peer in - updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in - return updated - }) - return updatedPeer - } - |> mapError { _ -> UpdatedRemotePeerError in - return .generic - } - } else { + guard let apiUser = result.first else { 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 in + if let peer = peer { + return .single(peer) + } else { + return .fail(.generic) + } + } } } else if case let .group(id) = peer { return network.request(Api.functions.messages.getChats(id: [id])) diff --git a/submodules/TelegramCore/Sources/TelegramUser.swift b/submodules/TelegramCore/Sources/TelegramUser.swift index c4e893972a..d152c205ee 100644 --- a/submodules/TelegramCore/Sources/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/TelegramUser.swift @@ -8,7 +8,7 @@ func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMedia var representations: [TelegramMediaImageRepresentation] = [] switch photo { case let .userProfilePhoto(flags, _, photoSmall, photoBig, dcId): - let hasVideo = (flags & (1 << 0)) != 0 + let _ = (flags & (1 << 0)) != 0 let smallResource: TelegramMediaResource let fullSizeResource: TelegramMediaResource @@ -138,4 +138,37 @@ extension TelegramUser { 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) + } + } } diff --git a/submodules/TelegramCore/Sources/UpdateAccountPeerName.swift b/submodules/TelegramCore/Sources/UpdateAccountPeerName.swift index b759ced39e..f46bc43b89 100644 --- a/submodules/TelegramCore/Sources/UpdateAccountPeerName.swift +++ b/submodules/TelegramCore/Sources/UpdateAccountPeerName.swift @@ -17,8 +17,9 @@ public func updateAccountPeerName(account: Account, firstName: String, lastName: |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Void in if let result = result { - let peer = TelegramUser(user: result) - updatePeers(transaction: transaction, peers: [peer], update: { $1 }) + if let peer = TelegramUser.merge(transaction.getPeer(result.peerId) as? TelegramUser, rhs: result) { + updatePeers(transaction: transaction, peers: [peer], update: { $1 }) + } } } } @@ -43,6 +44,7 @@ public func updateAbout(account: Account, about: String?) -> Signal mapError { _ -> UpdateAboutError in return .generic } + } + |> castError(UpdateAboutError.self) } } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index e94467f96f..6a3f8e49d5 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -173,13 +173,14 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI return postbox.transaction { transaction -> Bool in switch result { case let .userFull(userFull): - let telegramUser = TelegramUser(user: userFull.user) - updatePeers(transaction: transaction, peers: [telegramUser], update: { _, updated -> Peer in - return updated - }) + if let telegramUser = TelegramUser.merge(transaction.getPeer(userFull.user.peerId) as? TelegramUser, rhs: userFull.user) { + updatePeers(transaction: transaction, peers: [telegramUser], update: { _, updated -> Peer in + return updated + }) + } transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFull.notifySettings)]) 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 @@ -267,10 +268,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } } for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence + if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } } } @@ -416,10 +418,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } } for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence + if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } } } @@ -427,10 +430,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI switch participantResult { case let .channelParticipant(_, users): for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence + if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } } } } diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 737e5b5c70..2112312b18 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -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) } -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 { return } @@ -77,7 +83,24 @@ private func updateMessageThreadStatsInternal(transaction: Transaction, threadMe loop: for j in 0 ..< attributes.count { if let attribute = attributes[j] as? ReplyThreadMessageAttribute { let count = max(0, attribute.count + countDifference) - attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), 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 { channelThreadMessageId = attribute.messageId } diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index 5a5253a588..6d07288965 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -44,6 +44,14 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?, transaction.updatePeersInternal(peers, update: { previous, updated in 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 { case Namespaces.Peer.CloudUser: break diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 8007f0a112..6587155840 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -303,9 +303,9 @@ public final class AccountContextImpl: AccountContext { switch location { case let .peer(peerId): return .peer(peerId) - case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId): - let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId) - return .external(messageId.peerId, context.state) + case let .replyThread(data): + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) + return .external(data.messageId.peerId, context.state) } } @@ -313,8 +313,8 @@ public final class AccountContextImpl: AccountContext { switch location { case .peer: return .single(nil) - case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId): - let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId) + case let .replyThread(data): + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) return context.maxReadOutgoingMessageId } } @@ -323,19 +323,19 @@ public final class AccountContextImpl: AccountContext { switch location { case .peer: let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start() - case let .replyThread(messageId, _, maxMessage, maxReadIncomingMessageId, maxReadOutgoingMessageId): - let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId) + case let .replyThread(data): + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) context.applyMaxReadIndex(messageIndex: messageIndex) } } } -private func chatLocationContext(holder: Atomic, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) -> ReplyThreadHistoryContext { +private func chatLocationContext(holder: Atomic, account: Account, data: ChatReplyThreadMessage) -> ReplyThreadHistoryContext { let holder = holder.modify { current in if let current = current as? ChatLocationContextHolderImpl { return current } else { - return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId) + return ChatLocationContextHolderImpl(account: account, data: data) } } as! ChatLocationContextHolderImpl return holder.context @@ -344,8 +344,8 @@ private func chatLocationContext(holder: Atomic, acc private final class ChatLocationContextHolderImpl: ChatLocationContextHolder { let context: ReplyThreadHistoryContext - init(account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) { - self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxMessage: maxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: maxReadOutgoingMessageId) + init(account: Account, data: ChatReplyThreadMessage) { + self.context = ReplyThreadHistoryContext(account: account, peerId: data.messageId.peerId, data: data) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5b50a7700e..f54f7fd6bf 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -69,8 +69,8 @@ extension ChatLocation { switch self { case let .peer(peerId): return peerId - case let .replyThread(messageId, _, _, _, _): - return messageId.peerId + case let .replyThread(replyThreadMessage): + return replyThreadMessage.messageId.peerId } } } @@ -371,16 +371,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .peer(peerId): locationBroadcastPanelSource = .peer(peerId) self.chatLocationInfoData = .peer(Promise()) - case let .replyThread(messageId, _, _, _, _): + case let .replyThread(replyThreadMessage): locationBroadcastPanelSource = .none let promise = Promise() - let key = PostboxViewKey.messages([messageId]) + let key = PostboxViewKey.messages([replyThreadMessage.messageId]) promise.set(context.account.postbox.combinedView(keys: [key]) |> map { views -> Message? in guard let view = views.views[key] as? MessagesView else { return nil } - return view.messages[messageId] + return view.messages[replyThreadMessage.messageId] }) self.chatLocationInfoData = .replyThread(promise) } @@ -481,8 +481,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } var openChatLocation = strongSelf.chatLocation - if case let .replyThread(messageId, _, _, _, _) = openChatLocation { - if message.threadId != makeMessageThreadId(messageId) { + if case let .replyThread(replyThreadMessage) = openChatLocation { + if message.threadId != makeMessageThreadId(replyThreadMessage.messageId) { openChatLocation = .peer(message.id.peerId) } } @@ -1601,7 +1601,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } if let strongSelf = self { - if case .replyThread(message.id, _, _, _, _) = strongSelf.chatLocation { + if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.messageId == message.id { return .none } @@ -1640,8 +1640,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch strongSelf.chatLocation { case let .peer(peerId): strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) - case let .replyThread(messageId, _, _, _, _): - let peerId = messageId.peerId + case let .replyThread(replyThreadMessage): + 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) } }, requestRedeliveryOfFailedMessages: { [weak self] id in @@ -2191,11 +2191,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if let navigationController = strongSelf.navigationController as? NavigationController { - 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) - } + strongSelf.openMessageReplies(messageId: messageId, isChannelPost: isChannelPost, atMessage: nil) }, openReplyThreadOriginalMessage: { [weak self] message in guard let strongSelf = self else { return @@ -2211,9 +2207,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let attribute = attribute as? SourceReferenceMessageAttribute { if let threadMessageId = threadMessageId { if let navigationController = strongSelf.navigationController as? NavigationController { - ChatControllerImpl.openMessageReplies(context: strongSelf.context, navigationController: navigationController, present: { c, a in - self?.present(c, in: .window(.root), with: a) - }, messageId: threadMessageId, isChannelPost: true, atMessage: attribute.messageId) + strongSelf.openMessageReplies(messageId: threadMessageId, isChannelPost: true, atMessage: attribute.messageId) } } else { strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId)) @@ -2614,7 +2608,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.preloadHistoryPeerId != peerDiscussionId { strongSelf.preloadHistoryPeerId = 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 { strongSelf.preloadHistoryPeerIdDisposable.set(nil) } @@ -2642,18 +2639,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) } else if case let .replyThread(messagePromise) = self.chatLocationInfoData { - let onlineMemberCount: Signal = .single(nil) let hasScheduledMessages: Signal = .single(false) self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - let isReplyThread: Bool let replyThreadType: ChatTitleContent.ReplyThreadType switch chatLocation { case .peer: replyThreadType = .replies - case let .replyThread(_, isChannelPost, _, _, _): - isReplyThread = true - if isChannelPost { + case let .replyThread(replyThreadMessage): + if replyThreadMessage.isChannelPost { replyThreadType = .comments } else { replyThreadType = .replies @@ -2997,8 +2991,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch self.chatLocation { case let .peer(peerId): activitySpace = PeerActivitySpace(peerId: peerId, threadId: nil) - case let .replyThread(threadMessageId, _, _, _, _): - activitySpace = PeerActivitySpace(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId)) + case let .replyThread(replyThreadMessage): + activitySpace = PeerActivitySpace(peerId: replyThreadMessage.messageId.peerId, threadId: makeMessageThreadId(replyThreadMessage.messageId)) } self.inputActivityDisposable = (self.typingActivityPromise.get() @@ -3416,11 +3410,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = cachedData as? CachedSecretChatData { } - if case let .replyThread(messageId, _, _, _, _) = strongSelf.chatLocation { + if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation { if isTopReplyThreadMessageShown { pinnedMessageId = nil } else { - pinnedMessageId = messageId + pinnedMessageId = replyThreadMessage.messageId } } @@ -4266,7 +4260,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, openCalendarSearch: { [weak self] in if let strongSelf = self { - let peerId = strongSelf.chatLocation.peerId strongSelf.chatDisplayNode.dismissInput() let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in @@ -4274,7 +4267,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } 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 { strongSelf.loadingMessage.set(false) if let messageId = messageId { @@ -5098,6 +5103,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current) + }, scrollToTop: { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory() }, viewReplies: { [weak self] sourceMessageId, replyThreadResult in guard let strongSelf = self else { return @@ -5105,7 +5116,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let navigationController = strongSelf.effectiveNavigationController { 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())) @@ -5168,8 +5179,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let postbox = self.context.account.postbox let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) var activityThreadId: Int64? - if case let .replyThread(messageId, _, _, _, _) = self.chatLocation { - activityThreadId = makeMessageThreadId(messageId) + if case let .replyThread(replyThreadMessage) = self.chatLocation { + activityThreadId = makeMessageThreadId(replyThreadMessage.messageId) } self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, threadId: activityThreadId)) |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in @@ -7566,8 +7577,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch self.chatLocation { case .peer: break - case let .replyThread(messageId, _, _, _, _): - defaultReplyMessageId = messageId + case let .replyThread(replyThreadMessage): + defaultReplyMessageId = replyThreadMessage.messageId } return messages.map { message in @@ -7609,13 +7620,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) { - let peerId: PeerId - switch self.chatLocation { - case let .peer(peerIdValue): - peerId = peerIdValue - case let .replyThread(messageId, _, _, _, _): - peerId = messageId.peerId - } + let peerId: PeerId = self.chatLocation.peerId if commit || !self.presentationInterfaceState.isScheduledMessages { self.commitPurposefulAction() @@ -8035,8 +8040,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch self.chatLocation { case .peer: break - case let .replyThread(messageId, _, _, _, _): - searchTopMsgId = messageId + case let .replyThread(replyThreadMessage): + searchTopMsgId = replyThreadMessage.messageId } switch search.domain { case .everything: @@ -8217,7 +8222,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G progressDisposable.dispose() } } - |> deliverOnMainQueue).start(next: { [weak self] index in + |> deliverOnMainQueue).start(next: { index in if index.1 { if !progressStarted { 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 { [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() { let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil 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?) { - let foundIndex = Promise() - foundIndex.set(fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId))) + private func openMessageReplies(messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?) { + guard let navigationController = self.navigationController as? NavigationController else { + return + } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let progressSignal: Signal = Signal { [weak self] _ in + guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { + return EmptyDisposable + } - var cancelImpl: (() -> Void)? - let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - present(statusController, nil) + let previousId = controllerInteraction.currentMessageWithLoadingReplyThread + controllerInteraction.currentMessageWithLoadingReplyThread = messageId + strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(messageId) + if let previousId = previousId { + 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() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] result in - statusController?.dismiss() + let progress = (progressSignal + |> delay(0.15, queue: .mainQueue())).start() + + 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 { + return Signal { subscriber in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if let result = result { - let chatLocation: ChatLocation = .replyThread(threadMessageId: result.message.messageId, isChannelPost: result.isChannelPost, maxMessage: result.message.maxMessage, maxReadIncomingMessageId: result.message.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.message.maxReadOutgoingMessageId) + var cancelImpl: (() -> Void)? + 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? 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)) + 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) { diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 0f8b36e2a5..db2e43d347 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -130,6 +130,7 @@ public final class ChatControllerInteraction { var stickerSettings: ChatInterfaceStickerSettings var searchTextHighightState: (String, [MessageIndex])? var seenOneTimeAnimatedMedia = Set() + var currentMessageWithLoadingReplyThread: MessageId? init( openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 2442e144bb..53e1bc33f4 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -136,10 +136,10 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } 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 { switch entry { - case let .message(id, messages) where id == messageId: + case let .message(id, messages) where id == replyThreadMessage.messageId: if !messages.isEmpty { let selection: ChatHistoryMessageSelection = .none @@ -172,7 +172,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, 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 default: diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 6acc103e14..8bcb045344 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -627,15 +627,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !isAuxiliaryChat { additionalData.append(.totalUnreadState) } - if case let .replyThread(messageId, _, _, _, _) = chatLocation { - additionalData.append(.cachedPeerData(messageId.peerId)) - additionalData.append(.peerNotificationSettings(messageId.peerId)) - if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { - additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: messageId.peerId))) - additionalData.append(.peer(messageId.peerId)) + if case let .replyThread(replyThreadMessage) = chatLocation { + additionalData.append(.cachedPeerData(replyThreadMessage.messageId.peerId)) + additionalData.append(.peerNotificationSettings(replyThreadMessage.messageId.peerId)) + if replyThreadMessage.messageId.peerId.namespace == Namespaces.Peer.CloudChannel { + additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: replyThreadMessage.messageId.peerId))) + additionalData.append(.peer(replyThreadMessage.messageId.peerId)) } - additionalData.append(.message(messageId)) + additionalData.append(.message(replyThreadMessage.messageId)) } let currentViewVersion = Atomic(value: nil) @@ -1173,7 +1173,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } - if case .replyThread(message.id, _, _, _, _) = self.chatLocation { + if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.messageId == message.id { isTopReplyThreadMessageShownValue = true } case let .MessageGroupEntry(_, messages, _): @@ -1203,7 +1203,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } - if case .replyThread(message.id, _, _, _, _) = self.chatLocation { + if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.messageId == message.id { isTopReplyThreadMessageShownValue = true } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index c077be162e..25856c97ae 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -302,44 +302,36 @@ enum ReplyThreadSubject { case groupMessage(MessageId) } -func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject) -> Signal { - let message: Signal +func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject, atMessageId: MessageId?) -> Signal { + let message: Signal switch subject { case let .channelPost(messageId): - message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId) + message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId, atMessageId: atMessageId) case let .groupMessage(messageId): - message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId) + message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId, atMessageId: atMessageId) } return message - |> mapToSignal { message -> Signal in - guard let message = message else { - return .single(nil) - } - - let isChannelPost: Bool - switch subject { - case .channelPost: - isChannelPost = true - case .groupMessage: - isChannelPost = false - } - + |> mapToSignal { replyThreadMessage -> Signal in let chatLocationContextHolder = Atomic(value: nil) - let preloadSignal = preloadedChatHistoryViewForLocation( - ChatHistoryLocationInput( - content: .Initial(count: 60), + let input: ChatHistoryLocationInput + if let atMessageId = atMessageId { + input = ChatHistoryLocationInput( + content: .InitialSearch(location: .id(atMessageId), count: 30), id: 0 - ), + ) + } else { + input = ChatHistoryLocationInput( + content: .Initial(count: 30), + id: 0 + ) + } + + let preloadSignal = preloadedChatHistoryViewForLocation( + input, context: context, - chatLocation: .replyThread( - threadMessageId: message.messageId, - isChannelPost: isChannelPost, - maxMessage: message.maxMessage, - maxReadIncomingMessageId: message.maxReadIncomingMessageId, - maxReadOutgoingMessageId: message.maxReadOutgoingMessageId - ), + chatLocation: .replyThread(replyThreadMessage), chatLocationContextHolder: chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, @@ -362,13 +354,14 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea } } |> take(1) - |> map { isEmpty -> ReplyThreadInfo? in + |> map { isEmpty -> ReplyThreadInfo in return ReplyThreadInfo( - message: message, - isChannelPost: isChannelPost, + message: replyThreadMessage, + isChannelPost: replyThreadMessage.isChannelPost, isEmpty: isEmpty, contextHolder: chatLocationContextHolder ) } + |> castError(FetchChannelReplyThreadMessageError.self) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 235f17be82..8d7c8e9709 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -468,8 +468,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } var isReplyThreadHead = false - if case let .replyThread(messageId, _, _, _, _) = chatPresentationInterfaceState.chatLocation { - isReplyThreadHead = messages[0].id == messageId + if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { + isReplyThreadHead = messages[0].id == replyThreadMessage.messageId } if !isReplyThreadHead, data.canReply { @@ -614,8 +614,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in let foundIndex = Promise() - if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info { - foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id)) + if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { + foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id, atMessageId: nil) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + }) } c.dismiss(completion: { 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) }, action: { _, f in var threadMessageId: MessageId? - if case let .replyThread(replyThread, _, _, _, _) = chatPresentationInterfaceState.chatLocation { - threadMessageId = replyThread + if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { + threadMessageId = replyThreadMessage.messageId } let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil) |> map { result -> String? in diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index c24abe7c06..a2509e1268 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -561,15 +561,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId, isChannelPost, _, _, _): - if messageId.peerId != item.context.account.peerId { - if messageId.peerId.isGroupOrChannel && item.message.author != nil { + case let .replyThread(replyThreadMessage): + if replyThreadMessage.messageId.peerId != item.context.account.peerId { + if replyThreadMessage.messageId.peerId.isGroupOrChannel && item.message.author != nil { var isBroadcastChannel = false if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { isBroadcastChannel = true } - if isChannelPost, messageId == item.message.id { + if replyThreadMessage.isChannelPost, replyThreadMessage.messageId == item.message.id { isBroadcastChannel = true } @@ -676,7 +676,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { 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 case let .replyThread(replyThreadMessageId, _, _, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { } else { 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 } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id { + return + } + if let selectionState = item.controllerInteraction.selectionState { var selected = false var incoming = true diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index f1e12efd59..ae544e5be4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -271,7 +271,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { 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 currentImage = self.media as? TelegramMediaImage let imageLayout = self.inlineImageNode.asyncLayout() @@ -284,7 +284,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { 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 fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0) @@ -323,7 +323,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } @@ -424,7 +424,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { refineContentImageLayout = refineLayout } 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 (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 contentInstantVideoSizeAndApply = (videoLayout, apply) } 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 } } else if let image = media as? TelegramMediaImage { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 05783efe5d..512bd94509 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -98,15 +98,17 @@ final class ChatMessageBubbleContentItem { let controllerInteraction: ChatControllerInteraction let message: Message let read: Bool + let chatLocation: ChatLocation let presentationData: ChatPresentationData let associatedData: ChatMessageItemAssociatedData 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.controllerInteraction = controllerInteraction self.message = message self.read = read + self.chatLocation = chatLocation self.presentationData = presentationData self.associatedData = associatedData self.attributes = attributes diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index c53601ecd5..f611982349 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -837,13 +837,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var hasAvatar = false var allowFullWidth = false - let chatLocationPeerId: PeerId - switch item.chatLocation { - case let .peer(peerId): - chatLocationPeerId = peerId - case let .replyThread(messageId, _, _, _, _): - chatLocationPeerId = messageId.peerId - } + let chatLocationPeerId: PeerId = item.chatLocation.peerId do { let peerId = chatLocationPeerId @@ -885,7 +879,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode 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 } @@ -912,7 +906,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) 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 } else if item.message.id.peerId == item.context.account.peerId { if let _ = sourceReference { @@ -1048,7 +1045,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = attribute.title } } else if let attribute = attribute as? ReplyMessageAttribute { - if case let .replyThread(replyThreadMessageId, _, _, _, _) = item.chatLocation, replyThreadMessageId == attribute.messageId { + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == attribute.messageId { } else { replyMessage = firstMessage.associatedMessages[attribute.messageId] } @@ -1140,7 +1137,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode 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? if case .mosaic = prepareContentPosition { @@ -1294,7 +1291,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } @@ -2953,6 +2950,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode default: break } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id { + canHaveSelection = false + } if let selectionState = item.controllerInteraction.selectionState, canHaveSelection { var selected = false diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift index 8117e3f692..cab139ee7a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import TelegramCore import SyncCore import TelegramPresentationData +import RadialStatusNode final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { private let separatorNode: ASDisplayNode @@ -17,6 +18,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { private let buttonNode: HighlightTrackingButtonNode private let avatarsNode: MergedAvatarsNode private let unreadIconNode: ASImageNode + private var statusNode: RadialStatusNode? required init() { self.separatorNode = ASDisplayNode() @@ -257,6 +259,32 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { 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)) strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index 4f63c7c143..01eca96f48 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -152,7 +152,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index 9512d2b705..110dc38a36 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble } 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) diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index cdefb5f241..c0ff0ffa39 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -39,7 +39,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent let text: String = item.message.text 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) diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index 406fb699b0..0d638f3fd4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont } 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) diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index dc0198b6de..bb6ef83495 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -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 (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) diff --git a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift index 43c68319f1..58cdd2513e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 391453130c..cb89ccbe66 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -182,8 +182,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { switch item.chatLocation { case let .peer(peerId): messagePeerId = peerId - case let .replyThread(messageId, _, _, _, _): - messagePeerId = messageId.peerId + case let .replyThread(replyThreadMessage): + messagePeerId = replyThreadMessage.messageId.peerId } do { @@ -194,7 +194,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { 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 } @@ -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) @@ -341,7 +341,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId, _, _, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { } else { 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 } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id { + return + } + if let selectionState = item.controllerInteraction.selectionState { var selected = false var incoming = true diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 2054b91a02..33f2ac521e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -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 titleAsyncLayout = TextNode.asyncLayout(self.titleNode) @@ -214,7 +214,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { 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 let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.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 } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } @@ -971,12 +971,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { 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() - 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 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 { fileNode = node @@ -986,7 +986,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { 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 let (finalWidth, finalLayout) = continueLayout(constrainedSize) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index eafec61d64..17b75280d3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -257,7 +257,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift index 022693b7c3..c30749172b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 5c46692cc5..7d85a5730f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -276,13 +276,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var effectiveAuthor: Peer? let displayAuthorInfo: Bool - let messagePeerId: PeerId - switch chatLocation { - case let .peer(peerId): - messagePeerId = peerId - case let .replyThread(messageId, _, _, _, _): - messagePeerId = messageId.peerId - } + let messagePeerId: PeerId = chatLocation.peerId do { 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 { 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 } if !hasActionMedia && !isBroadcastChannel { diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 6440354556..5fb7fdb35d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -182,7 +182,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index ebaf889c65..7e94647085 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -183,7 +183,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index 721e616c3d..aafdd77778 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1030,7 +1030,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index b10af32878..dfdd041beb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -61,7 +61,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { viewCount = attribute.count } else if let attribute = attribute as? RestrictedContentMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 93e9d44a4e..094f051f5b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -249,15 +249,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId, isChannelPost, _, _, _): - if messageId.peerId != item.context.account.peerId { - if messageId.peerId.isGroupOrChannel && item.message.author != nil { + case let .replyThread(replyThreadMessage): + if replyThreadMessage.messageId.peerId != item.context.account.peerId { + if replyThreadMessage.messageId.peerId.isGroupOrChannel && item.message.author != nil { var isBroadcastChannel = false if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { isBroadcastChannel = true } - if isChannelPost, messageId == item.message.id { + if replyThreadMessage.isChannelPost, replyThreadMessage.messageId == item.message.id { isBroadcastChannel = true } @@ -360,7 +360,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { 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 case let .replyThread(replyThreadMessageId, _, _, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { } else { 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 } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == item.message.id { + return + } + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) var isEmoji = false if let item = self.item, item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) { diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index f21468cb52..fe3adf5011 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -115,7 +115,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { 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 { dateReplies = Int(attribute.count) } diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index bb141ab5c2..e8068b2e85 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index c941bbf08d..0db13197a9 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -121,6 +121,7 @@ final class ChatPanelInterfaceInteraction { let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let openPeersNearby: () -> Void let unarchivePeer: () -> Void + let scrollToTop: () -> Void let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? @@ -196,6 +197,7 @@ final class ChatPanelInterfaceInteraction { openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, + scrollToTop: @escaping () -> Void, viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { @@ -270,6 +272,7 @@ final class ChatPanelInterfaceInteraction { self.openPeersNearby = openPeersNearby self.displaySearchResultsTooltip = displaySearchResultsTooltip self.unarchivePeer = unarchivePeer + self.scrollToTop = scrollToTop self.viewReplies = viewReplies self.statuses = statuses } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index d400751579..4cddcd270a 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -299,11 +299,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { @objc func tapped() { if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage { if self.isReplyThread { - if let sourceReference = message.sourceReference { + interfaceInteraction.scrollToTop() + /*if let sourceReference = message.sourceReference { interfaceInteraction.navigateToMessage(sourceReference.messageId, true) } else { interfaceInteraction.navigateToMessage(message.id, false) - } + }*/ } else { interfaceInteraction.navigateToMessage(message.id, false) } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index 8e48bf1547..3ed5932fce 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -126,7 +126,9 @@ final class ChatRecentActionsController: TelegramBaseController { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) + }, unarchivePeer: { + }, scrollToTop: { + }, viewReplies: { _, _ in }, statuses: nil) self.navigationItem.titleView = self.titleView diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 5016816ad1..f0f3c671ca 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -817,9 +817,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let navigationController = strongSelf.getNavigationController() { 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() { - 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): let packReference: StickerPackReference = .name(name) diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index 2038a12862..0f88c5822b 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -190,7 +190,6 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { } } if case .replyThread = interfaceState.chatLocation { - self.calendarButton.isHidden = true canSearchMembers = false } self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 368328c967..2cb92739b2 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -792,8 +792,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder - } else if case let .replyThread(_, isChannelPost, _, _, _) = interfaceState.chatLocation { - if isChannelPost { + } else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation { + if replyThreadMessage.isChannelPost { placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholderReply diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index d5ed38a1d9..233d8cd066 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -125,8 +125,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if message.id.peerId == peerId { return true } - case let .replyThread(messageId, _, _, _, _): - if message.id.peerId == messageId.peerId { + case let .replyThread(replyThreadMessage): + if message.id.peerId == replyThreadMessage.messageId.peerId { return true } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 02d4a45bd0..8ddbdc9500 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -90,11 +90,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur navigationController?.pushViewController(controller) case let .channelMessage(peerId, messageId): 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 { - ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in + let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in present(c, a) - }, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId) + }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() } case let .stickerPack(name): dismissInput() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b7876c5dfb..44ae82c9a5 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -431,7 +431,9 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) + }, unarchivePeer: { + }, scrollToTop: { + }, viewReplies: { _, _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index bf96129d42..fbabf1690d 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -59,11 +59,11 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate if let navigationController = controller.navigationController as? NavigationController { 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 { - 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) - }, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId) + }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() } case let .stickerPack(name): let packReference: StickerPackReference = .name(name) diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index d0728f6c98..75a45279eb 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -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))) case let .replyThread(id, replyId): 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 in + return .single(nil) + } |> map { result -> ResolvedUrl? in guard let result = result else { 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 { @@ -367,12 +371,16 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig if let foundPeer = foundPeer { if let threadId = threadId { let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) - return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId) + return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId, atMessageId: nil) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } |> map { result -> ResolvedUrl? in guard let result = result else { return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId) } - return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxMessage: result.maxMessage, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.maxReadIncomingMessageId, messageId: messageId) + return .replyThreadMessage(replyThreadMessage: result, messageId: messageId) } } else { return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(messageId), peekData: nil))) diff --git a/third-party/webrtc/webrtc-ios b/third-party/webrtc/webrtc-ios index e4d49e73cd..11255bcfff 160000 --- a/third-party/webrtc/webrtc-ios +++ b/third-party/webrtc/webrtc-ios @@ -1 +1 @@ -Subproject commit e4d49e73cd8206518e7b3dd75d54af0f0ef5b810 +Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877