Telegram-iOS/submodules/TelegramUI/Sources/GalleryHiddenMediaManager.swift
isaac 69bfc65da7 Postbox refactor waves 138–276: 168-commit squash
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>
2026-05-05 00:44:27 +02:00

174 lines
5.4 KiB
Swift

import Foundation
import UIKit
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import AccountContext
private final class GalleryHiddenMediaContext {
private var ids = Set<Int32>()
func add(id: Int32) {
self.ids.insert(id)
}
func remove(id: Int32) {
self.ids.remove(id)
}
var isEmpty: Bool {
return self.ids.isEmpty
}
}
private final class GalleryHiddenMediaTargetHolder {
weak var target: GalleryHiddenMediaTarget?
init(target: GalleryHiddenMediaTarget?) {
self.target = target
}
}
final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager {
private final class SourceContext {
let disposable: Disposable
var state: (GalleryHiddenMediaId, Int32)? = nil
init(disposable: Disposable) {
self.disposable = disposable
}
}
private var sources = Bag<Void>()
private var sourceContexts: [Int: SourceContext] = [:]
private var nextId: Int32 = 0
private var contexts: [GalleryHiddenMediaId: GalleryHiddenMediaContext] = [:]
private var subscribers = Bag<(Set<GalleryHiddenMediaId>) -> Void>()
private var targets: [GalleryHiddenMediaTargetHolder] = []
func hiddenIds() -> Signal<Set<GalleryHiddenMediaId>, NoError> {
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
Queue.mainQueue().async {
if let strongSelf = self {
subscriber.putNext(Set(strongSelf.contexts.keys))
let index = strongSelf.subscribers.add({ next in
subscriber.putNext(next)
})
disposable.set(ActionDisposable {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.subscribers.remove(index)
}
}
})
}
}
return disposable
}
}
private func withContext(id: GalleryHiddenMediaId, _ f: (GalleryHiddenMediaContext) -> Void) {
let context: GalleryHiddenMediaContext
if let current = self.contexts[id] {
context = current
} else {
context = GalleryHiddenMediaContext()
self.contexts[id] = context
}
let wasEmpty = context.isEmpty
f(context)
if context.isEmpty {
self.contexts.removeValue(forKey: id)
}
if context.isEmpty != wasEmpty {
let allIds = Set(self.contexts.keys)
for subscriber in self.subscribers.copyItems() {
subscriber(allIds)
}
}
}
func addSource(_ signal: Signal<GalleryHiddenMediaId?, NoError>) -> Int {
let index = self.sources.add(Void())
let disposable = MetaDisposable()
let context = SourceContext(disposable: disposable)
self.sourceContexts[index] = context
disposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self, weak context] id in
guard let strongSelf = self, let context = context else {
return
}
if id != context.state?.0 {
if let (previousId, previousIndex) = context.state {
strongSelf.removeHiddenMedia(id: previousId, index: previousIndex)
context.state = nil
}
if let id = id {
context.state = (id, strongSelf.addHiddenMedia(id: id))
}
}
}))
return index
}
func removeSource(_ index: Int) {
self.sources.remove(index)
if let context = self.sourceContexts.removeValue(forKey: index) {
context.disposable.dispose()
if let (previousId, previousIndex) = context.state {
self.removeHiddenMedia(id: previousId, index: previousIndex)
}
}
}
func addTarget(_ target: GalleryHiddenMediaTarget) {
self.targets.append(GalleryHiddenMediaTargetHolder(target: target))
}
func removeTarget(_ target: GalleryHiddenMediaTarget) {
for i in (0 ..< self.targets.count).reversed() {
let holderTarget = self.targets[i].target
if holderTarget == nil || holderTarget === target {
self.targets.remove(at: i)
}
}
}
func findTarget(messageId: EngineMessage.Id, media: EngineMedia) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? {
for i in (0 ..< self.targets.count).reversed() {
if let holderTarget = self.targets[i].target {
if let result = holderTarget.getTransitionInfo(messageId: messageId, media: media) {
return result
}
} else {
self.targets.remove(at: i)
}
}
return nil
}
private func addHiddenMedia(id: GalleryHiddenMediaId) -> Int32 {
let itemId = self.nextId
self.nextId += 1
self.withContext(id: id, { context in
context.add(id: itemId)
})
return itemId
}
private func removeHiddenMedia(id: GalleryHiddenMediaId, index: Int32) {
self.withContext(id: id, { context in
context.remove(id: index)
})
}
}