mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
86d1456552
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>
140 lines
7.2 KiB
Swift
140 lines
7.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import ContextUI
|
|
import UndoUI
|
|
import AccountContext
|
|
import ChatControllerInteraction
|
|
import AnimatedTextComponent
|
|
import ChatMessagePaymentAlertController
|
|
import TelegramPresentationData
|
|
import TelegramNotices
|
|
|
|
extension ChatControllerImpl {
|
|
func presentPaidMessageAlertIfNeeded(count: Int32 = 1, forceDark: Bool = false, alwaysAsk: Bool = false, completion: @escaping (Bool) -> Void) {
|
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap(EnginePeer.init) else {
|
|
completion(false)
|
|
return
|
|
}
|
|
guard let renderedPeer = self.presentationInterfaceState.renderedPeer.flatMap(EngineRenderedPeer.init) else {
|
|
return
|
|
}
|
|
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars, self.presentationInterfaceState.interfaceState.editMessage == nil {
|
|
let totalAmount = sendPaidMessageStars.value * Int64(count)
|
|
|
|
let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { [weak self] dismissedAmount in
|
|
guard let self, let starsContext = self.context.starsContext else {
|
|
return
|
|
}
|
|
if !alwaysAsk, let dismissedAmount, dismissedAmount == sendPaidMessageStars.value, let currentState = starsContext.currentState, currentState.balance.value > totalAmount {
|
|
if count < 3 && totalAmount < 100 {
|
|
completion(false)
|
|
} else {
|
|
completion(true)
|
|
self.displayPaidMessageUndo(count: count, amount: sendPaidMessageStars)
|
|
}
|
|
} else {
|
|
var presentationData = self.presentationData
|
|
if forceDark {
|
|
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
|
}
|
|
var peer = peer
|
|
var renderedPeer = renderedPeer
|
|
if let peerDiscussionId = self.presentationInterfaceState.peerDiscussionId, let channel = self.contentData?.state.peerView?.peers[peerDiscussionId] {
|
|
peer = EnginePeer(channel)
|
|
renderedPeer = EngineRenderedPeer(peer: peer)
|
|
}
|
|
let controller = chatMessagePaymentAlertController(
|
|
context: self.context,
|
|
presentationData: presentationData,
|
|
updatedPresentationData: nil,
|
|
peers: [renderedPeer],
|
|
count: count,
|
|
amount: sendPaidMessageStars,
|
|
totalAmount: nil,
|
|
hasCheck: !alwaysAsk,
|
|
navigationController: self.navigationController as? NavigationController,
|
|
completion: { [weak self] dontAskAgain in
|
|
guard let self else {
|
|
return
|
|
}
|
|
|
|
if dontAskAgain {
|
|
let _ = ApplicationSpecificNotice.setDismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id, amount: sendPaidMessageStars.value).start()
|
|
}
|
|
|
|
if let currentState = starsContext.currentState, currentState.balance.value < totalAmount {
|
|
let _ = (self.context.engine.payments.starsTopUpOptions()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] options in
|
|
guard let self else {
|
|
return
|
|
}
|
|
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), targetPeerId: nil, customTheme: nil, completion: { stars in
|
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
|
let _ = (starsContext.onUpdate
|
|
|> deliverOnMainQueue).start(next: {
|
|
completion(false)
|
|
})
|
|
})
|
|
self.push(controller)
|
|
})
|
|
} else {
|
|
completion(false)
|
|
}
|
|
}
|
|
)
|
|
self.present(controller, in: .window(.root))
|
|
}
|
|
})
|
|
} else {
|
|
completion(false)
|
|
}
|
|
}
|
|
|
|
func displayPaidMessageUndo(count: Int32, amount: StarsAmount) {
|
|
guard let peerId = self.chatLocation.peerId else {
|
|
return
|
|
}
|
|
|
|
if let current = self.currentPaidMessageUndoController {
|
|
self.currentPaidMessageUndoController = nil
|
|
current.dismiss()
|
|
|
|
self.context.engine.messages.forceSendPostponedPaidMessage(peerId: peerId)
|
|
}
|
|
|
|
let title = self.presentationData.strings.Chat_PaidMessage_Sent_Title(count)
|
|
let text = self.presentationData.strings.Chat_PaidMessage_Sent_Text(self.presentationData.strings.Chat_PaidMessage_Sent_Text_Stars(Int32(clamping: amount.value * Int64(count)))).string
|
|
let textItems: [AnimatedTextComponent.Item] = [
|
|
AnimatedTextComponent.Item(id: 0, content: .text(text))
|
|
]
|
|
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak self] action in
|
|
guard let self else {
|
|
return false
|
|
}
|
|
if case .undo = action {
|
|
var messageIds: [EngineMessage.Id] = []
|
|
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ChatMessageItemNodeProtocol {
|
|
for message in itemNode.messages() {
|
|
if message.id.namespace == Namespaces.Message.Local {
|
|
messageIds.append(message.id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: messageIds, type: .forLocalPeer).startStandalone()
|
|
}
|
|
return false
|
|
})
|
|
self.currentPaidMessageUndoController = controller
|
|
self.present(controller, in: .current)
|
|
}
|
|
}
|