mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Consolidates 137 wave commits + 31 supporting commits (CLAUDE.md bump, typealias additions, AnyObject→EngineMedia restoration) into one squashed commit. Migrates dozens of consumer-side public APIs, struct fields, protocol methods, and enum payloads from Postbox protocols/structs to TelegramEngine engine wrappers and typealiases. Drops `import Postbox` from many files. Adds new TelegramCore typealiases and one TelegramEngineUnauthorized facade. Notable changes by category: **TelegramCore typealias additions** (rule 2 — narrow utility typealiases): - EngineChatListIndex, EngineTempBoxFile, EngineItemCollectionItemIndex, EngineItemCollectionViewEntryIndex, EngineValueBoxEncryptionParameters, EngineMessageAndThreadId, EnginePeerStoryStats, EngineMessageHistoryAnchorIndex, EngineChatListTotalUnreadStateCategory, EngineChatListTotalUnreadStateStats, EnginePeerSummaryCounterTags, EngineChatListTotalUnreadState, EngineItemCacheEntryId, EngineHashFunctions, EngineCachedMediaResourceRepresentationResult, EngineMediaResourceDataFetchResult, EngineMediaResourceDataFetchError, EngineMediaResourceStatus, EngineCachedPeerData **TelegramCore engine extensions/forwarders**: - EngineMessage.engineMedia, EngineMessage.enginePeers, EngineMessage.adAttribute, EngineMessage.effectivelyIncoming - engineFileSize forwarder - TelegramEngine.Resources.clearCachedMediaResources(mediaResourceIds: Set<EngineMediaResource.Id>) - TelegramEngine.Resources.fetchStatus(id:resourceSize:) - TelegramEngineUnauthorized.UnauthorizedResources facade with storeResourceData **Public API/struct migrations to engine types**: - ChatAvailableMessageActions.banAuthor/banAuthors → EnginePeer?/[EnginePeer] - WebSessionsContextState.peers → [EnginePeer.Id: EnginePeer] - CacheUsageStats.peers → [EnginePeer.Id: EnginePeer] - PeerCommand.peer → EnginePeer - PeerInfoControllerMode.calls(messages:) → [EngineMessage] - CallControllerNodeProtocol.updatePeer → EnginePeer params - ChatHistoryListNode.messageInCurrentHistoryView (and 4 variants) → EngineMessage? - ChatHistorySearchContainerNode.messageForGallery → EngineMessage? - PeerInfoPaneNode.findLoadedMessage / ensureMessageIsVisible / transitionNodeForGallery → engine-typed - GalleryHiddenMediaTarget.getTransitionInfo / GalleryHiddenMediaManager.findTarget → engine-typed - ChatPanelInterfaceInteraction.presentReactionDeletionOptions / presentBan*MessageOptions → EnginePeer - DrawingMessageRenderer.messages → [EngineMessage] - ChatVideoGalleryItemScrubberView.setFetchStatusSignal → EngineMediaResource.FetchStatus - ChannelDiscussionGroupActionSheetItem.peer, VoiceChatPeerEntry.peer, VoiceChatFullscreenParticipantItem.peer, MediaStreamComponent.chatPeer, MediaStreamVideoComponent.callPeer, ChatMessageContactBubbleContentNode.contactPeer, ChatMessageForwardInfoNode.peer, ChatMessageCommentFooterContentNode.replyPeers, ChatReportPeerTitlePanelNode.peer, ChatMessageActionUrlAuthController.bot, PeerMediaCollectionInterfaceState.peer, ChatMessageCallBubbleContentNode.peopleAvatars, ChatLoadingNode.renderedPeer (→ EngineRenderedPeer) — all to engine types **Wave-71-shadow stored-field migrations** (Postbox Peer/Message → Engine wrapper): - LegacyCallControllerNode.peer - CallStatusBarNode.currentPeer **Dead-code / dead-field removals**: - CallController.peer, CallControllerNodeV2.account, ContactMultiselectionController PeerNameIndex fields, preparedChatListNodeViewTransition account: Account param, FetchResource.swift entirely (unused function) **Module-level Postbox import drops**: 30+ files including TelegramRootController, EditStories, GiftViewScreen, AnimatedStickerUtils, FetchPhotoLibraryImageResource, PeerInfoGiftsPaneNode, PeerInfoPaneContainerNode, PresentAddMembers, PeerInfoProfileItems, ChatControllerAdminBanUsers, PresentationData typealiases, DefaultDayPresentationTheme, ChatListViewTransition, GalleryHiddenMediaManager, RecentSessionsController, GifContext, AuthorizationSequenceController, PeerInfoHeaderEditingContentNode, PeerInfoHeaderNode, PeerAllowedReactionListController, CallControllerNodeV2, and 6 PeerInfo pane files. **AnyObject restoration**: rule 8 added (never substitute Postbox protocols with Any/AnyObject) — undid previous AnyObject substitutions in waves 141/143 back to EngineMedia. Doc maintenance: CLAUDE.md updated to reflect new typealiases and forwarders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
185 lines
9.3 KiB
Swift
185 lines
9.3 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Display
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import AccountContext
|
|
|
|
public func instantPageAndAnchor(message: EngineMessage) -> (TelegramMediaWebpage, String?)? {
|
|
for media in message.media {
|
|
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
|
if let _ = content.instantPage {
|
|
var textUrl: String?
|
|
if let pageUrl = URL(string: content.url) {
|
|
inner: for attribute in message.attributes {
|
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
|
for entity in attribute.entities {
|
|
switch entity.type {
|
|
case let .TextUrl(url):
|
|
if let parsedUrl = URL(string: url) {
|
|
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
|
|
textUrl = url
|
|
}
|
|
}
|
|
case .Url:
|
|
let nsText = message.text as NSString
|
|
var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
|
if entityRange.location + entityRange.length > nsText.length {
|
|
entityRange.location = max(0, nsText.length - entityRange.length)
|
|
entityRange.length = nsText.length - entityRange.location
|
|
}
|
|
let url = nsText.substring(with: entityRange)
|
|
if let parsedUrl = URL(string: url) {
|
|
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
|
|
textUrl = url
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
break inner
|
|
}
|
|
}
|
|
}
|
|
var anchor: String?
|
|
if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") {
|
|
anchor = String(textUrl[anchorRange.upperBound...])
|
|
}
|
|
|
|
return (webpage, anchor)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public final class InstantPageController: ViewController {
|
|
private let context: AccountContext
|
|
private var webPage: TelegramMediaWebpage
|
|
private let sourceLocation: InstantPageSourceLocation
|
|
private let anchor: String?
|
|
|
|
private var presentationData: PresentationData
|
|
|
|
private let _ready = Promise<Bool>()
|
|
override public var ready: Promise<Bool> {
|
|
return self._ready
|
|
}
|
|
|
|
private var controllerNode: InstantPageControllerNode {
|
|
return self.displayNode as! InstantPageControllerNode
|
|
}
|
|
|
|
private var webpageDisposable: Disposable?
|
|
private var storedStateDisposable: Disposable?
|
|
|
|
private var settings: InstantPagePresentationSettings?
|
|
private var settingsDisposable: Disposable?
|
|
private var themeSettings: PresentationThemeSettings?
|
|
|
|
public init(context: AccountContext, webPage: TelegramMediaWebpage, sourceLocation: InstantPageSourceLocation, anchor: String? = nil) {
|
|
self.context = context
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
self.webPage = webPage
|
|
self.anchor = anchor
|
|
self.sourceLocation = sourceLocation
|
|
|
|
super.init(navigationBarPresentationData: nil)
|
|
|
|
self.navigationPresentation = .modalInLargeLayout
|
|
|
|
self.statusBar.statusBarStyle = .White
|
|
|
|
self.webpageDisposable = (actualizedWebpage(account: context.account, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] result in
|
|
if let strongSelf = self {
|
|
strongSelf.webPage = result
|
|
if strongSelf.isNodeLoaded {
|
|
strongSelf.controllerNode.updateWebPage(result, anchor: strongSelf.anchor)
|
|
}
|
|
}
|
|
})
|
|
|
|
self.settingsDisposable = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.instantPagePresentationSettings, ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|
|
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
|
|
if let strongSelf = self {
|
|
let settings: InstantPagePresentationSettings
|
|
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.instantPagePresentationSettings]?.get(InstantPagePresentationSettings.self) {
|
|
settings = current
|
|
} else {
|
|
settings = InstantPagePresentationSettings.defaultSettings
|
|
}
|
|
let themeSettings: PresentationThemeSettings
|
|
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
|
|
themeSettings = current
|
|
} else {
|
|
themeSettings = PresentationThemeSettings.defaultSettings
|
|
}
|
|
|
|
strongSelf.settings = settings
|
|
strongSelf.themeSettings = themeSettings
|
|
if strongSelf.isNodeLoaded {
|
|
strongSelf.controllerNode.update(settings: settings, themeSettings: themeSettings, strings: strongSelf.presentationData.strings)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.webpageDisposable?.dispose()
|
|
self.storedStateDisposable?.dispose()
|
|
self.settingsDisposable?.dispose()
|
|
}
|
|
|
|
override public func viewWillDisappear(_ animated: Bool) {
|
|
let _ = updateInstantPageStoredStateInteractively(engine: self.context.engine, webPage: self.webPage, state: self.controllerNode.currentState).start()
|
|
}
|
|
|
|
override public func loadDisplayNode() {
|
|
self.displayNode = InstantPageControllerNode(controller: self, context: self.context, settings: self.settings, themeSettings: self.themeSettings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, autoNightModeTriggered: self.presentationData.autoNightModeTriggered, statusBar: self.statusBar, sourceLocation: self.sourceLocation, getNavigationController: { [weak self] in
|
|
return self?.navigationController as? NavigationController
|
|
}, present: { [weak self] c, a in
|
|
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
|
}, pushController: { [weak self] c in
|
|
(self?.navigationController as? NavigationController)?.pushViewController(c)
|
|
}, openPeer: { [weak self] peer in
|
|
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), animated: true))
|
|
}
|
|
}, navigateBack: { [weak self] in
|
|
if let strongSelf = self, let controllers = strongSelf.navigationController?.viewControllers.reversed() {
|
|
for controller in controllers {
|
|
if !(controller is InstantPageController) {
|
|
strongSelf.navigationController?.popToViewController(controller, animated: true)
|
|
return
|
|
}
|
|
}
|
|
strongSelf.navigationController?.popViewController(animated: true)
|
|
}
|
|
})
|
|
|
|
self.storedStateDisposable = (instantPageStoredState(engine: self.context.engine, webPage: self.webPage)
|
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
|
if let strongSelf = self {
|
|
strongSelf.controllerNode.updateWebPage(strongSelf.webPage, anchor: strongSelf.anchor, state: state)
|
|
strongSelf._ready.set(.single(true))
|
|
}
|
|
})
|
|
|
|
self.displayNodeDidLoad()
|
|
}
|
|
|
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
|
}
|
|
}
|