Comments update

This commit is contained in:
Ali 2020-09-22 18:50:38 +04:00
parent d8ef15ee00
commit 95b12456dd
39 changed files with 1203 additions and 736 deletions

View File

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

View File

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

View File

@ -6,7 +6,7 @@ public enum ChatLocationInput {
case external(PeerId, Signal<MessageHistoryViewExternalInput, NoError>)
}
enum ResolvedChatLocationInput {
public enum ResolvedChatLocationInput {
case peer(PeerId)
case external(MessageHistoryViewExternalInput)
}

View File

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

View File

@ -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<MessageId.Id>) {
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<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
fileprivate func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
var mainPeerIdForTopTaggedMessages: PeerId?
switch peerIds {

View File

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

View File

@ -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,32 @@ public final class AccountViewTracker {
}
}
public struct UpdatedMessageReplyInfo {
var timestamp: Int32
var commentsPeerId: PeerId
var maxReadIncomingMessageId: MessageId?
var maxMessageId: MessageId?
}
public func replyInfoForMessageId(_ id: MessageId) -> Signal<UpdatedMessageReplyInfo?, NoError> {
return Signal { [weak self] subscriber in
let state = self?.updatedViewCountMessageIdsAndTimestamps[id]
let result = state?.result
if let state = state, let result = result, let commentsPeerId = result.commentsPeerId {
subscriber.putNext(UpdatedMessageReplyInfo(timestamp: state.timestamp, commentsPeerId: commentsPeerId, maxReadIncomingMessageId: result.maxReadIncomingMessageId, maxMessageId: result.maxMessageId))
} else {
subscriber.putNext(nil)
}
subscriber.putCompletion()
return EmptyDisposable
}
|> runOn(self.queue)
}
public func updateViewCountForMessageIds(messageIds: Set<MessageId>, clientId: Int32) {
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 +638,128 @@ public final class AccountViewTracker {
self.nextUpdatedViewCountDisposableId += 1
if let account = self.account {
let signal = (account.postbox.transaction { transaction -> Signal<Void, NoError> 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<Api.messages.MessageViews?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
guard case let .messageViews(viewCounts, chats, users)? = result else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for apiUser in users {
if let user = TelegramUser.merge(transaction.getPeer(apiUser.peerId) as? TelegramUser, rhs: apiUser) {
peers.append(user)
if let presence = TelegramUserPresence(apiUser: apiUser) {
peerPresences[user.id] = presence
}
}
}
for chat in chats {
if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) {
peers.append(groupOrChannel)
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
for i in 0 ..< messageIds.count {
if i < viewCounts.count {
if case let .messageViews(_, views, forwards, replies) = viewCounts[i] {
transaction.updateMessage(messageIds[i], update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
var foundReplies = false
var commentsChannelId: PeerId?
var recentRepliersPeerIds: [PeerId]?
var repliesCount: Int32?
var repliesMaxId: Int32?
var repliesReadMaxId: Int32?
if let replies = replies {
switch replies {
case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, maxId, readMaxId):
if let channelId = channelId {
commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
}
repliesCount = repliesCountValue
if let recentRepliers = recentRepliers {
recentRepliersPeerIds = recentRepliers.map { $0.peerId }
} else {
recentRepliersPeerIds = nil
}
repliesMaxId = maxId
repliesReadMaxId = readMaxId
}
}
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ViewCountMessageAttribute {
if let views = views {
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
}
} else if let _ = attributes[j] as? ForwardCountMessageAttribute {
if let forwards = forwards {
attributes[j] = ForwardCountMessageAttribute(count: Int(forwards))
}
} else if let _ = attributes[j] as? ReplyThreadMessageAttribute {
foundReplies = true
if let repliesCount = repliesCount {
attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId)
}
}
}
if !foundReplies, let repliesCount = repliesCount {
attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId))
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
}
}
}
}
} else {
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<Api.messages.MessageViews?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[MessageId: ViewCountContextState], NoError> in
guard case let .messageViews(viewCounts, chats, users)? = result else {
return .complete()
}
return account.postbox.transaction { transaction -> [MessageId: ViewCountContextState] in
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
var resultStates: [MessageId: ViewCountContextState] = [:]
for apiUser in users {
if let user = TelegramUser.merge(transaction.getPeer(apiUser.peerId) as? TelegramUser, rhs: apiUser) {
peers.append(user)
if let presence = TelegramUserPresence(apiUser: apiUser) {
peerPresences[user.id] = presence
}
}
}
for chat in chats {
if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) {
peers.append(groupOrChannel)
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
for i in 0 ..< messageIds.count {
if i < viewCounts.count {
if case let .messageViews(_, views, forwards, replies) = viewCounts[i] {
transaction.updateMessage(messageIds[i], update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
var foundReplies = false
var commentsChannelId: PeerId?
var recentRepliersPeerIds: [PeerId]?
var repliesCount: Int32?
var repliesMaxId: Int32?
var repliesReadMaxId: Int32?
if let replies = replies {
switch replies {
case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, maxId, readMaxId):
if let channelId = channelId {
commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
}
repliesCount = repliesCountValue
if let recentRepliers = recentRepliers {
recentRepliersPeerIds = recentRepliers.map { $0.peerId }
} else {
recentRepliersPeerIds = nil
}
repliesMaxId = maxId
repliesReadMaxId = readMaxId
}
}
var maxReadIncomingMessageId: MessageId?
var maxMessageId: MessageId?
if let commentsChannelId = commentsChannelId {
if let repliesReadMaxId = repliesReadMaxId {
maxReadIncomingMessageId = MessageId(peerId: commentsChannelId, namespace: Namespaces.Message.Cloud, id: repliesReadMaxId)
}
if let repliesMaxId = repliesMaxId {
maxMessageId = MessageId(peerId: commentsChannelId, namespace: Namespaces.Message.Cloud, id: repliesMaxId)
}
}
resultStates[messageIds[i]] = ViewCountContextState(timestamp: Int32(CFAbsoluteTimeGetCurrent()), clientId: clientId, result: ViewCountContextState.ReplyInfo(commentsPeerId: commentsChannelId, maxReadIncomingMessageId: maxReadIncomingMessageId, maxMessageId: maxMessageId))
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ViewCountMessageAttribute {
if let views = views {
attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views)))
}
} else if let _ = attributes[j] as? ForwardCountMessageAttribute {
if let forwards = forwards {
attributes[j] = ForwardCountMessageAttribute(count: Int(forwards))
}
} else if let _ = attributes[j] as? ReplyThreadMessageAttribute {
foundReplies = true
if let repliesCount = repliesCount {
attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId)
}
}
}
if !foundReplies, let repliesCount = repliesCount {
attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId, maxMessageId: repliesMaxId, maxReadMessageId: repliesReadMaxId))
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
}
}
return resultStates
}
}
}
|> switchToLatest)
|> afterDisposed { [weak self] in
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)
}
}
}

View File

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

View File

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

View File

@ -102,7 +102,7 @@ private final class HistoryPreloadEntry: Comparable {
|> mapToSignal { download -> Signal<Never, NoError> in
switch hole.hole {
case let .peer(peerHole):
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, 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
}
}

View File

@ -125,7 +125,39 @@ func withResolvedAssociatedMessages<T>(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<IndexSet, NoError> {
enum FetchMessageHistoryHoleThreadInput: CustomStringConvertible {
case direct(peerId: PeerId, threadId: Int64?)
case threadFromChannel(channelMessageId: MessageId)
var description: String {
switch self {
case let .direct(peerId, threadId):
return "direct(\(peerId), \(String(describing: threadId))"
case let .threadFromChannel(channelMessageId):
return "threadFromChannel(peerId: \(channelMessageId.peerId), postId: \(channelMessageId.id)"
}
}
var requestThreadId: MessageId? {
switch self {
case let .direct(peerId, threadId):
if let threadId = threadId {
return makeThreadIdMessageId(peerId: peerId, threadId: threadId)
} else {
return nil
}
case let .threadFromChannel(channelMessageId):
return channelMessageId
}
}
}
struct FetchMessageHistoryHoleResult: Equatable {
var removedIndices: IndexSet
var strictRemovedIndices: IndexSet
}
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal<FetchMessageHistoryHoleResult, NoError> {
let count = min(100, rawCount)
return postbox.stateView()
@ -137,389 +169,377 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
}
}
|> take(1)
|> mapToSignal { _ -> Signal<IndexSet, NoError> 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<FetchMessageHistoryHoleResult, NoError> 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<FetchMessageHistoryHoleResult, NoError> in
guard let inputPeer = inputPeer else {
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet()))
}
return (transaction.getPeer(peerId), hash)
}
|> mapToSignal { (peer, hash) -> Signal<IndexSet, NoError> 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<Api.messages.Messages, MTRpcError>
var implicitelyFillHole = false
let minMaxRange: ClosedRange<MessageId.Id>
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<Api.messages.Messages, MTRpcError>
var implicitelyFillHole = false
let minMaxRange: ClosedRange<MessageId.Id>
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<IndexSet, NoError> in
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]
var channelPts: Int32?
switch result {
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
channelPts = pts
case .messagesNotModified:
messages = []
chats = []
users = []
}
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers.append(groupOrChannel)
}
}
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
var storeMessages: [StoreMessage] = []
for message in messages {
if let storeMessage = StoreMessage(apiMessage: message, namespace: namespace) {
if let channelPts = channelPts {
var attributes = storeMessage.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
} else {
storeMessages.append(storeMessage)
}
}
}
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> IndexSet in
let _ = transaction.addMessages(storeMessages, location: .Random)
let _ = transaction.addMessages(additionalMessages, location: .Random)
var filledRange: ClosedRange<MessageId.Id>
let ids = storeMessages.compactMap { message -> MessageId.Id? in
switch message.id {
case let .Id(id):
switch space {
case let .tag(tag):
if !message.tags.contains(tag) {
return nil
} else {
return id.id
}
case .everywhere:
return id.id
}
case .Partial:
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<FetchMessageHistoryHoleResult, NoError> in
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]
var channelPts: Int32?
switch result {
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
channelPts = pts
case .messagesNotModified:
messages = []
chats = []
users = []
}
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers.append(groupOrChannel)
}
}
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
var storeMessages: [StoreMessage] = []
for message in messages {
if let storeMessage = StoreMessage(apiMessage: message, namespace: namespace) {
if let channelPts = channelPts {
var attributes = storeMessage.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
} else {
storeMessages.append(storeMessage)
}
}
}
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult in
let _ = transaction.addMessages(storeMessages, location: .Random)
let _ = transaction.addMessages(additionalMessages, location: .Random)
var filledRange: ClosedRange<MessageId.Id>
var strictFilledIndices: IndexSet
let ids = storeMessages.compactMap { message -> MessageId.Id? in
switch message.id {
case let .Id(id):
switch space {
case let .tag(tag):
if !message.tags.contains(tag) {
return nil
} else {
return id.id
}
case .everywhere:
return id.id
}
case .Partial:
return nil
}
}
if ids.count == 0 || implicitelyFillHole {
filledRange = minMaxRange
strictFilledIndices = IndexSet()
} else {
let messageRange = ids.min()! ... ids.max()!
switch direction {
case let .aroundId(aroundId):
filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound)
strictFilledIndices = IndexSet(integersIn: Int(min(aroundId.id, messageRange.lowerBound)) ... Int(max(aroundId.id, messageRange.upperBound)))
if peerInput.requestThreadId != nil {
if ids.count <= count / 2 - 1 {
filledRange = minMaxRange
}
}
case let .range(start, end):
if start.id <= end.id {
let minBound = start.id
let maxBound = messageRange.upperBound
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
var maxStrictIndex = max(minBound, maxBound)
maxStrictIndex = min(maxStrictIndex, messageRange.upperBound)
strictFilledIndices = IndexSet(integersIn: Int(min(minBound, maxBound)) ... Int(maxStrictIndex))
} else {
let minBound = messageRange.lowerBound
let maxBound = start.id
filledRange = min(minBound, maxBound) ... max(minBound, maxBound)
var maxStrictIndex = max(minBound, maxBound)
maxStrictIndex = min(maxStrictIndex, messageRange.upperBound)
strictFilledIndices = IndexSet(integersIn: Int(min(minBound, maxBound)) ... Int(maxStrictIndex))
}
}
}
switch peerInput {
case let .direct(peerId, threadId):
if let threadId = threadId {
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
}
}
}

View File

@ -55,7 +55,7 @@ func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox
for (entry, disposable) in added {
switch entry.hole {
case let .peer(hole):
disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, 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())
}
}
})

View File

@ -43,35 +43,31 @@ private class ReplyThreadHistoryContextImpl {
private var readStateDisposable: 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 +104,8 @@ 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)
}
})
}
@ -129,7 +125,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 +135,11 @@ private class ReplyThreadHistoryContextImpl {
}
}
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<IndexSet, NoError> {
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<FetchMessageHistoryHoleResult, NoError> {
switch entry.hole {
case let .peer(hole):
let fetchCount = min(entry.count, 100)
return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount)
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)
}
}
@ -257,10 +253,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 +267,59 @@ 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<ChatReplyThreadMessage?, NoError> {
public enum FetchChannelReplyThreadMessageError {
case generic
}
public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<ChatReplyThreadMessage?, NoError> in
|> castError(FetchChannelReplyThreadMessageError.self)
|> mapToSignal { inputPeer -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
guard let inputPeer = inputPeer else {
return .single(nil)
return .fail(.generic)
}
let discussionMessage: Signal<Api.messages.DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
let replyInfo = Promise<AccountViewTracker.UpdatedMessageReplyInfo?>()
replyInfo.set(account.viewTracker.replyInfoForMessageId(messageId))
struct DiscussionMessage {
public var messageId: MessageId
public var isChannelPost: Bool
public var maxMessage: MessageId?
public var maxReadIncomingMessageId: MessageId?
public var maxReadOutgoingMessageId: MessageId?
}
let remoteDiscussionMessageSignal: Signal<DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.DiscussionMessage?, NoError> in
return .single(nil)
}
return discussionMessage
|> mapToSignal { discussionMessage -> Signal<ChatReplyThreadMessage?, NoError> in
|> mapToSignal { discussionMessage -> Signal<DiscussionMessage?, NoError> 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 +354,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 +379,172 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
}
}
}
let discussionMessageSignal = (replyInfo.get()
|> mapToSignal { replyInfo -> Signal<DiscussionMessage?, NoError> in
guard let replyInfo = replyInfo else {
return .single(nil)
}
return account.postbox.transaction { transaction -> DiscussionMessage? in
let isParticipant = transaction.getPeerChatListIndex(replyInfo.commentsPeerId) != nil
guard isParticipant else {
return nil
}
var foundDiscussionMessageId: MessageId?
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
for attribute in attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if attribute.messageId == messageId {
foundDiscussionMessageId = id
return true
}
}
}
if foundDiscussionMessageId != nil {
return false
}
return true
})
guard let discussionMessageId = foundDiscussionMessageId else {
return nil
}
return DiscussionMessage(
messageId: discussionMessageId,
isChannelPost: true,
maxMessage: replyInfo.maxMessageId,
maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId,
maxReadOutgoingMessageId: nil
)
}
})
|> mapToSignal { result -> Signal<DiscussionMessage?, NoError> in
if let result = result {
return .single(result)
} else {
return remoteDiscussionMessageSignal
}
}
let discussionMessage = Promise<DiscussionMessage?>()
discussionMessage.set(discussionMessageSignal)
let preloadedHistoryPosition: Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, MessageId?, MessageId?), FetchChannelReplyThreadMessageError> = replyInfo.get()
|> 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, replyInfo.maxReadIncomingMessageId, replyInfo.maxMessageId)
}
|> castError(FetchChannelReplyThreadMessageError.self)
} else {
return discussionMessage.get()
|> 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, discussionMessage.maxReadIncomingMessageId, discussionMessage.maxMessage))
}
}
}
let preloadedHistory = preloadedHistoryPosition
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, aroundMessageId, maxMessageId -> Signal<FetchMessageHistoryHoleResult, FetchChannelReplyThreadMessageError> in
guard let maxMessageId = maxMessageId else {
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(integersIn: 1 ..< Int(Int32.max - 1)), strictRemovedIndices: IndexSet()))
}
return account.postbox.transaction { transaction -> Signal<FetchMessageHistoryHoleResult, FetchChannelReplyThreadMessageError> in
if let threadMessageId = threadMessageId {
var holes = transaction.getThreadIndexHoles(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId), namespace: Namespaces.Message.Cloud)
holes.remove(integersIn: Int(maxMessageId.id + 1) ..< Int(Int32.max))
let isParticipant = transaction.getPeerChatListIndex(commentsPeerId) != nil
if isParticipant {
let historyHoles = transaction.getHoles(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud)
holes.formIntersection(historyHoles)
}
let anchor: HistoryViewInputAnchor
if let aroundMessageId = aroundMessageId {
anchor = .message(aroundMessageId)
} else {
anchor = .upperBound
}
let testView = transaction.getMessagesHistoryViewState(
input: .external(MessageHistoryViewExternalInput(
peerId: commentsPeerId,
threadId: makeMessageThreadId(threadMessageId),
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
holes: [
Namespaces.Message.Cloud: holes
]
)),
count: 30,
clipHoles: true,
anchor: anchor,
namespaces: .not(Namespaces.Message.allScheduled)
)
if !testView.isLoading {
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet()))
}
}
let direction: MessageHistoryViewRelativeHoleDirection
if let aroundMessageId = aroundMessageId {
direction = .aroundId(aroundMessageId)
} else {
direction = .range(start: MessageId(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud, id: Int32.max - 1), end: MessageId(peerId: commentsPeerId, namespace: Namespaces.Message.Cloud, id: 1))
}
return fetchMessageHistoryHole(accountPeerId: account.peerId, source: .network(account.network), postbox: account.postbox, peerInput: peerInput, namespace: Namespaces.Message.Cloud, direction: direction, space: .everywhere, count: 30)
|> castError(FetchChannelReplyThreadMessageError.self)
}
|> castError(FetchChannelReplyThreadMessageError.self)
|> switchToLatest
}
return combineLatest(
discussionMessage.get()
|> castError(FetchChannelReplyThreadMessageError.self),
preloadedHistory
)
|> mapToSignal { discussionMessage, initialFilledHoles -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
guard let discussionMessage = discussionMessage else {
return .fail(.generic)
}
return account.postbox.transaction { transaction -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
for range in initialFilledHoles.strictRemovedIndices.rangeView {
transaction.removeThreadIndexHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound))
}
return .single(ChatReplyThreadMessage(
messageId: discussionMessage.messageId,
isChannelPost: discussionMessage.isChannelPost,
maxMessage: discussionMessage.maxMessage,
maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId,
maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId,
initialFilledHoles: initialFilledHoles.removedIndices
))
}
|> castError(FetchChannelReplyThreadMessageError.self)
|> switchToLatest
}
}
}

View File

@ -626,19 +626,26 @@ public func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerRefe
return .generic
}
|> mapToSignal { result -> Signal<Peer, UpdatedRemotePeerError> 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<Peer, UpdatedRemotePeerError> 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]))

View File

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

View File

@ -17,8 +17,9 @@ public func updateAccountPeerName(account: Account, firstName: String, lastName:
|> mapToSignal { result -> Signal<Void, NoError> 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<Void, Update
return current
}
})
} |> mapError { _ -> UpdateAboutError in return .generic }
}
|> castError(UpdateAboutError.self)
}
}

View File

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

View File

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

View File

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

View File

@ -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<ChatLocationContextHolder?>, account: Account, messageId: MessageId, maxMessage: ChatReplyThreadMessage.MaxMessage, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?) -> ReplyThreadHistoryContext {
private func chatLocationContext(holder: Atomic<ChatLocationContextHolder?>, account: Account, data: ChatReplyThreadMessage) -> ReplyThreadHistoryContext {
let holder = holder.modify { current in
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<ChatLocationContextHolder?>, 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)
}
}

View File

@ -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<Message?>()
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))
@ -2642,18 +2636,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}))
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData {
let onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
let hasScheduledMessages: Signal<Bool, NoError> = .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 +2988,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 +3407,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
}
}
@ -5105,7 +5096,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 +5159,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 +7557,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 +7600,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 +8020,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:
@ -8251,41 +8236,101 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
static func openMessageReplies(context: AccountContext, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?) {
let foundIndex = Promise<ReplyThreadInfo?>()
foundIndex.set(fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId)))
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<Never, NoError> = 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<Never, NoError> {
return Signal { subscriber in
let foundIndex = Promise<ReplyThreadInfo?>()
foundIndex.set(fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId))
|> map(Optional.init)
|> `catch` { _ -> Signal<ReplyThreadInfo?, NoError> in
return .single(nil)
})
if let result = result {
let chatLocation: ChatLocation = .replyThread(threadMessageId: result.message.messageId, isChannelPost: result.isChannelPost, maxMessage: result.message.maxMessage, maxReadIncomingMessageId: result.message.maxReadIncomingMessageId, maxReadOutgoingMessageId: result.message.maxReadOutgoingMessageId)
let subject: ChatControllerSubject?
if let atMessageId = atMessageId {
subject = .message(atMessageId)
} else {
subject = nil
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var cancelImpl: (() -> Void)?
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
if displayModalProgress {
present(statusController, nil)
}
let disposable = (foundIndex.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
if displayModalProgress {
statusController?.dismiss()
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty, keepStack: .always))
if let result = result {
let chatLocation: ChatLocation = .replyThread(result.message)
let subject: ChatControllerSubject?
if let atMessageId = atMessageId {
subject = .message(atMessageId)
} else {
subject = nil
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty, keepStack: .always))
subscriber.putCompletion()
}
})
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) {

View File

@ -130,6 +130,7 @@ public final class ChatControllerInteraction {
var stickerSettings: ChatInterfaceStickerSettings
var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>()
var currentMessageWithLoadingReplyThread: MessageId?
init(
openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool,

View File

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

View File

@ -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<Int?>(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
}
}

View File

@ -302,8 +302,8 @@ enum ReplyThreadSubject {
case groupMessage(MessageId)
}
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject) -> Signal<ReplyThreadInfo?, NoError> {
let message: Signal<ChatReplyThreadMessage?, NoError>
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject) -> Signal<ReplyThreadInfo, FetchChannelReplyThreadMessageError> {
let message: Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError>
switch subject {
case let .channelPost(messageId):
message = fetchChannelReplyThreadMessage(account: context.account, messageId: messageId)
@ -312,19 +312,7 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
}
return message
|> mapToSignal { message -> Signal<ReplyThreadInfo?, NoError> 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<ReplyThreadInfo, FetchChannelReplyThreadMessageError> in
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let preloadSignal = preloadedChatHistoryViewForLocation(
@ -333,13 +321,7 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
id: 0
),
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 +344,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)
}
}

View File

@ -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<ChatReplyThreadMessage?>()
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info {
foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id))
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel {
foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id)
|> map(Optional.init)
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> 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

View File

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

View File

@ -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
}
@ -1048,7 +1042,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]
}
@ -2953,6 +2947,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
present(c, a)
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true)
}
case let .stickerPack(name):
dismissInput()

View File

@ -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
controller?.present(c, in: .window(.root), with: a)
}, messageId: replyThreadMessageId, isChannelPost: isChannelPost, atMessage: messageId)
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true)
}
case let .stickerPack(name):
let packReference: StickerPackReference = .name(name)

View File

@ -322,11 +322,15 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
case let .replyThread(id, replyId):
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId)
|> map(Optional.init)
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> 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 {
@ -368,11 +372,15 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
if let threadId = threadId {
let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId)
return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId)
|> map(Optional.init)
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> 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)))

@ -1 +1 @@
Subproject commit e4d49e73cd8206518e7b3dd75d54af0f0ef5b810
Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877