31 waves of consumer-side migration from `import Postbox` to TelegramEngine typealiases. Net: 173 import drops + 39 BUILD-dep drops + 1 new typealias (`EngineStoryId = StoryId`, wave 113). Wave shapes used: - Orphan-import sweeps (107, 108, 128): drop `import Postbox` from files whose only Postbox-symbol reference was the import line itself, then resolve build failures. Methodology requires token-level (`grep -oE`) filtering, not line-level, to avoid masking real Postbox usage on lines that also contain `Namespaces.X` references. - Identifier-swap mini-waves (109-127, 129-134, 136-137): rename Postbox-typealiased identifiers to engine equivalents (PeerId -> EnginePeer.Id, MessageId -> EngineMessage.Id, MediaId -> EngineMedia.Id, MessageIndex -> EngineMessage.Index, StoryId -> EngineStoryId, ItemCollectionId -> EngineItemCollectionId, PreferencesEntry -> EnginePreferencesEntry, FetchResourceSourceType/Error -> EngineFetchResourceSourceType/Error, MemoryBuffer -> EngineMemoryBuffer, MessageTags -> EngineMessage.Tags, MessageAttribute -> EngineMessage.Attribute, TempBox -> EngineTempBox). - Asset-string FP-only orphans (124). - Typealias addition + drain (113): added `EngineStoryId` typealias to TelegramCore, then drained 3+11 consumer sites. Hard blockers identified during these waves (must restore `import Postbox` when present): MediaResource[A-Za-z]* (any suffix -- the literal `MediaResource` matches don't catch MediaResourceData/MediaResourceId/etc.), Postbox/MediaBox/MediaResource raw types, PostboxCoding/PostboxEncoder/ PostboxDecoder, TempBoxFile, ValueBoxKey, PostboxView, combinedView, HashFunctions, postboxLog, openPostbox, declareEncodable, PeerView, MessageHistoryView, MessageHistoryThreadData, CachedPeerData, RenderedPeer, SelectivePrivacyPeer, SimpleDictionary, ItemCollectionInfosView, ItemCollectionItem, ItemCollectionItemIndex, ItemCollectionViewEntryIndex, ChatListIndex, ChatListEntrySummaryComponents, CodableEntry, MessageHistoryThread, MessageHistoryAnchorIndex, MessageHistoryEntryLocation, PeerStoryStats, PeerNameIndex, PeerSummaryCounterTags, ChatListTotalUnreadStateCategory/Stats, arePeersEqual. Protocol-shape blockers: bare `Peer`/`Message`/`Media` in function signatures, generic args, enum-case payloads, or dict value types (e.g., `[PeerId: Peer]`, `case messages([Message])`, `Signal<(Peer?, ...), NoError>`). `replace_all PeerId -> EnginePeer.Id` is dangerous: mangles compound names like `failedPeerId`, `ContactListPeerId`, `nextRemoteMediaId`, `replyToMessageId`. Pre-flight grep `\b[a-z][a-zA-Z]*PeerId\b` and only replace_all if 0 matches. Also removes unneeded design/plan docs from a separate (link-highlighting) feature branch: - docs/superpowers/plans/2026-05-02-link-highlighting-modern-path-fixes.md - docs/superpowers/specs/2026-05-02-link-highlighting-modern-path-fixes-design.md Squashed commits: 6d82c2980d..e6de5d53a3 (59 commits, including per-wave content commits and per-wave CLAUDE.md bumps). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109 lines
6.2 KiB
Swift
109 lines
6.2 KiB
Swift
import Foundation
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import PresentationDataUtils
|
|
import Display
|
|
|
|
extension ChatControllerImpl {
|
|
func openMessageReplies(messageId: EngineMessage.Id, displayProgressInMessage: EngineMessage.Id?, isChannelPost: Bool, atMessage atMessageId: EngineMessage.Id?, displayModalProgress: Bool) {
|
|
guard let navigationController = self.effectiveNavigationController else {
|
|
return
|
|
}
|
|
|
|
if let displayProgressInMessage = displayProgressInMessage, self.controllerInteraction?.currentMessageWithLoadingReplyThread == displayProgressInMessage {
|
|
return
|
|
}
|
|
|
|
let _ = self.presentVoiceMessageDiscardAlert(action: {
|
|
let progressSignal: Signal<Never, NoError> = Signal { [weak self] _ in
|
|
guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
|
|
return EmptyDisposable
|
|
}
|
|
|
|
if let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread != displayProgressInMessage {
|
|
let previousId = controllerInteraction.currentMessageWithLoadingReplyThread
|
|
controllerInteraction.currentMessageWithLoadingReplyThread = displayProgressInMessage
|
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
|
|
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 let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread == displayProgressInMessage {
|
|
controllerInteraction.currentMessageWithLoadingReplyThread = nil
|
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|> runOn(.mainQueue())
|
|
|
|
let progress = (progressSignal
|
|
|> delay(0.15, queue: .mainQueue())).startStrict()
|
|
|
|
self.navigationActionDisposable.set((ChatControllerImpl.openMessageReplies(context: self.context, updatedPresentationData: self.updatedPresentationData, navigationController: navigationController, present: { [weak self] c, a in
|
|
self?.present(c, in: .window(.root), with: a)
|
|
}, messageId: messageId, isChannelPost: isChannelPost, atMessage: atMessageId, displayModalProgress: displayModalProgress)
|
|
|> afterDisposed {
|
|
progress.dispose()
|
|
}).startStrict())
|
|
})
|
|
}
|
|
|
|
static func openMessageReplies(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: EngineMessage.Id, isChannelPost: Bool, atMessage atMessageId: EngineMessage.Id?, displayModalProgress: Bool) -> Signal<Never, NoError> {
|
|
return Signal { subscriber in
|
|
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 = (fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId), atMessageId: atMessageId, preload: true)
|
|
|> deliverOnMainQueue).startStrict(next: { [weak statusController] result in
|
|
if displayModalProgress {
|
|
statusController?.dismiss()
|
|
}
|
|
|
|
let chatLocation: NavigateToChatControllerParams.Location = .replyThread(result.message)
|
|
|
|
let subject: ChatControllerSubject?
|
|
if let atMessageId = atMessageId {
|
|
subject = .message(id: .id(atMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)
|
|
} else if let index = result.scrollToLowerBoundMessage {
|
|
subject = .message(id: .id(index.id), highlight: nil, timecode: nil, setupReply: false)
|
|
} else {
|
|
subject = nil
|
|
}
|
|
|
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty ? .text : nil, keepStack: .always))
|
|
subscriber.putCompletion()
|
|
}, error: { _ in
|
|
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
|
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_DiscussionMessageUnavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
})
|
|
|
|
cancelImpl = { [weak statusController] in
|
|
disposable.dispose()
|
|
statusController?.dismiss()
|
|
subscriber.putCompletion()
|
|
}
|
|
|
|
return ActionDisposable {
|
|
cancelImpl?()
|
|
}
|
|
}
|
|
|> runOn(.mainQueue())
|
|
}
|
|
}
|