diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 65508cd3de..1a6d901517 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -596,16 +596,16 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { return (state, filterId) }) - if self.controlsHistoryPreload { + if self.controlsHistoryPreload, case .chatList(groupId: .root) = self.location { self.context.account.viewTracker.chatListPreloadItems.set(combineLatest(queue: .mainQueue(), context.sharedContext.hasOngoingCall.get(), itemNode.listNode.preloadItems.get() ) - |> map { hasOngoingCall, preloadItems -> [ChatHistoryPreloadItem] in + |> map { hasOngoingCall, preloadItems -> Set in if hasOngoingCall { return [] } else { - return preloadItems + return Set(preloadItems) } }) } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 3888ca3751..f085c1710c 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2955,7 +2955,7 @@ final class PostboxImpl { return ActionDisposable { [weak self] in disposable.dispose() if let strongSelf = self { - strongSelf.queue.async { + strongSelf.queue.justDispatch { strongSelf.viewTracker.removeMessageHistoryView(index: index) } } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 156d74ae59..dfad00a94b 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1562,7 +1562,7 @@ public final class AccountViewTracker { let (pollMessageIds, pollMessageDict) = pollMessages(entries: next.0.entries) strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict) if case let .peer(peerId, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) } else if case let .thread(peerId, _, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) } @@ -1576,7 +1576,7 @@ public final class AccountViewTracker { switch chatLocation { case let .peer(peerId, _): if peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: chatLocation) } case let .thread(peerId, _, _): if peerId.namespace == Namespaces.Peer.CloudChannel { @@ -1694,7 +1694,7 @@ public final class AccountViewTracker { if let strongSelf = self { strongSelf.queue.async { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:]) - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil) } } }) diff --git a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift index e2cde230dc..ce3b14a45e 100644 --- a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift @@ -137,7 +137,13 @@ final class HistoryViewStateValidationContexts { self.accountPeerId = accountPeerId } - func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) { + func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput?) { + #if DEBUG + if "".isEmpty { + return + } + #endif + assert(self.queue.isCurrent()) guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.unseenReaction || view.tagMask == MessageTags.music || view.tagMask == MessageTags.pinned else { if self.contexts[id] != nil { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 1b38507b70..04c6db137d 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -271,6 +271,10 @@ struct FetchMessageHistoryHoleResult: Equatable { func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal { let count = min(100, rawCount) + if peerInput.requestThreadId != nil, case .everywhere = space, case .aroundId = direction { + assert(true) + } + return postbox.stateView() |> mapToSignal { view -> Signal in if let state = view.state as? AuthorizedAccountState { @@ -694,6 +698,12 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH return nil } } + + print("fetchMessageHistoryHole for \(peerInput) space \(space) done") + if peerInput.requestThreadId != nil, case .everywhere = space, case .aroundId = direction { + assert(true) + } + if ids.count == 0 || implicitelyFillHole { filledRange = minMaxRange strictFilledIndices = IndexSet() @@ -742,8 +752,6 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - print("fetchMessageHistoryHole for \(peerInput) space \(space) done") - let result = FetchMessageHistoryHoleResult( removedIndices: IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)), strictRemovedIndices: strictFilledIndices, diff --git a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift index 893190a18f..f78c8b9ff4 100644 --- a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift @@ -3,46 +3,148 @@ import Postbox import SwiftSignalKit private final class ManagedMessageHistoryHolesState { - private var holeDisposables: [MessageHistoryHolesViewEntry: Disposable] = [:] + private struct LocationKey: Equatable { + var peerId: PeerId + var threadId: Int64? + var space: MessageHistoryHoleSpace + + init(peerId: PeerId, threadId: Int64?, space: MessageHistoryHoleSpace) { + self.peerId = peerId + self.threadId = threadId + self.space = space + } + } + + private struct PendingEntry { + var key: LocationKey + var entry: MessageHistoryHolesViewEntry + var disposable: Disposable + + init(key: LocationKey, entry: MessageHistoryHolesViewEntry, disposable: Disposable) { + self.key = key + self.entry = entry + self.disposable = disposable + } + } + + private struct DiscardedEntry { + var entry: PendingEntry + var timestamp: Double + + init(entry: PendingEntry, timestamp: Double) { + self.entry = entry + self.timestamp = timestamp + } + } + + private var pendingEntries: [PendingEntry] = [] + private var discardedEntries: [DiscardedEntry] = [] + + private let performWork: (@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void + private var oldEntriesTimer: SwiftSignalKit.Timer? + + init(performWork: @escaping (@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void) { + self.performWork = performWork + } + + deinit { + self.oldEntriesTimer?.invalidate() + } func clearDisposables() -> [Disposable] { - let disposables = Array(self.holeDisposables.values) - self.holeDisposables.removeAll() + var disposables = Array(self.pendingEntries.map(\.disposable)) + disposables.append(contentsOf: self.discardedEntries.map(\.entry.disposable)) + self.pendingEntries.removeAll() + self.discardedEntries.removeAll() return disposables } - func update(entries: Set) -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable]) { - var removed: [Disposable] = [] + private func updateNeedsTimer() { + let needsTimer = !self.discardedEntries.isEmpty + if needsTimer { + if self.oldEntriesTimer == nil { + let performWork = self.performWork + self.oldEntriesTimer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { + performWork { impl in + let disposables = impl.discardOldEntries() + for disposable in disposables { + disposable.dispose() + } + } + }, queue: .mainQueue()) + self.oldEntriesTimer?.start() + } + } else if let oldEntriesTimer = self.oldEntriesTimer { + self.oldEntriesTimer = nil + oldEntriesTimer.invalidate() + } + } + + private func discardOldEntries() -> [Disposable] { + let timestamp = CFAbsoluteTimeGetCurrent() + + var result: [Disposable] = [] + for i in (0 ..< self.discardedEntries.count).reversed() { + if self.discardedEntries[i].timestamp < timestamp - 0.5 { + result.append(self.discardedEntries[i].entry.disposable) + self.discardedEntries.remove(at: i) + } + } + + return result + } + + func update(entries: Set) -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable], hasOldEntries: Bool) { + let removed: [Disposable] = [] var added: [MessageHistoryHolesViewEntry: MetaDisposable] = [:] - for (entry, disposable) in self.holeDisposables { - if !entries.contains(entry) { - removed.append(disposable) - self.holeDisposables.removeValue(forKey: entry) + let timestamp = CFAbsoluteTimeGetCurrent() + + for i in (0 ..< self.pendingEntries.count).reversed() { + if !entries.contains(self.pendingEntries[i].entry) { + self.discardedEntries.append(DiscardedEntry(entry: self.pendingEntries[i], timestamp: timestamp)) + self.pendingEntries.remove(at: i) + //removed.append(self.pendingEntries[i].disposable) } } for entry in entries { switch entry.hole { - case .peer: - if self.holeDisposables[entry] == nil { - let disposable = MetaDisposable() - self.holeDisposables[entry] = disposable - added[entry] = disposable + case let .peer(peerHole): + let key = LocationKey(peerId: peerHole.peerId, threadId: peerHole.threadId, space: entry.space) + if !self.pendingEntries.contains(where: { $0.key == key }) { + if let discardedIndex = self.discardedEntries.firstIndex(where: { $0.entry.entry == entry }) { + let discardedEntry = self.discardedEntries.remove(at: discardedIndex) + self.pendingEntries.append(discardedEntry.entry) + } else { + let disposable = MetaDisposable() + self.pendingEntries.append(PendingEntry(key: key, entry: entry, disposable: disposable)) + added[entry] = disposable + } } } } - return (removed, added) + self.updateNeedsTimer() + + return (removed, added, !self.discardedEntries.isEmpty) } } func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return Signal { _ in - let state = Atomic(value: ManagedMessageHistoryHolesState()) + var performWorkImpl: ((@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void)? + let state = Atomic(value: ManagedMessageHistoryHolesState(performWork: { f in + performWorkImpl?(f) + })) + performWorkImpl = { [weak state] f in + state?.with { state in + f(state) + } + } let disposable = postbox.messageHistoryHolesView().start(next: { view in - let (removed, added) = state.with { state -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable]) in + let (removed, added, _) = state.with { state in return state.update(entries: view.entries) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b728c81953..6e576cb3fe 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5789,7 +5789,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var index: Int } - let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 3) + let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 10) |> map { update -> TopMessage? in switch update { case .Loading: @@ -5813,7 +5813,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let loadCount = 100 + let loadCount = 10 struct PinnedHistory { struct PinnedMessage { diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index f64cbc03a9..655c937f3b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -495,7 +495,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { return } var messageId: MessageId? - if let messageReference = messageReference, case let .message(_, id, _, _, _) = messageReference.content { + if let messageReference = messageReference, case let .message(_, _, id, _, _, _) = messageReference.content { messageId = id } strongSelf.controllerInteraction.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) @@ -641,7 +641,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { @objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { if case .ended = recognizer.state { - if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content { + if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _) = self.messageReference?.content { self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame) } else if let peer = self.peer { if let adMessageId = self.adMessageId { diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index a017c0d106..b0d2f47c75 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -173,16 +173,16 @@ private final class PrefetchManagerInnerImpl { if case .full = automaticDownload { if let image = media as? TelegramMediaImage { - context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start()) + context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start()) } else if let _ = media as? TelegramMediaWebFile { //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) } else if let file = media as? TelegramMediaFile { - let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority) + let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority) context.fetchDisposable.set(fetchSignal.start()) } } else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat { if let file = media as? TelegramMediaFile, let _ = file.size { - context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start()) + context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start()) } } }