mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +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>
221 lines
9.4 KiB
Swift
221 lines
9.4 KiB
Swift
import Foundation
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Display
|
|
import MergeLists
|
|
import SearchUI
|
|
import TelegramUIPreferences
|
|
|
|
struct ChatListNodeView {
|
|
let originalList: EngineChatList
|
|
let filteredEntries: [ChatListNodeEntry]
|
|
let isLoading: Bool
|
|
let filter: ChatListFilter?
|
|
}
|
|
|
|
enum ChatListNodeViewTransitionReason {
|
|
case initial
|
|
case interactiveChanges
|
|
case holeChanges
|
|
case reload
|
|
}
|
|
|
|
struct ChatListNodeViewTransitionInsertEntry {
|
|
let index: Int
|
|
let previousIndex: Int?
|
|
let entry: ChatListNodeEntry
|
|
let directionHint: ListViewItemOperationDirectionHint?
|
|
}
|
|
|
|
struct ChatListNodeViewTransitionUpdateEntry {
|
|
let index: Int
|
|
let previousIndex: Int
|
|
let entry: ChatListNodeEntry
|
|
let directionHint: ListViewItemOperationDirectionHint?
|
|
}
|
|
|
|
struct ChatListNodeViewTransition {
|
|
let chatListView: ChatListNodeView
|
|
let deleteItems: [ListViewDeleteItem]
|
|
let insertEntries: [ChatListNodeViewTransitionInsertEntry]
|
|
let updateEntries: [ChatListNodeViewTransitionUpdateEntry]
|
|
let options: ListViewDeleteAndInsertOptions
|
|
let scrollToItem: ListViewScrollToItem?
|
|
let stationaryItemRange: (Int, Int)?
|
|
let adjustScrollToFirstItem: Bool
|
|
let animateCrossfade: Bool
|
|
}
|
|
|
|
public enum ChatListNodeViewScrollPosition {
|
|
case index(index: EngineChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
|
|
}
|
|
|
|
func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, previewing: Bool, disableAnimations: Bool, scrollPosition: ChatListNodeViewScrollPosition?, searchMode: Bool, forceAllUpdated: Bool) -> Signal<ChatListNodeViewTransition, NoError> {
|
|
return Signal<ChatListNodeViewTransition, NoError> { subscriber in
|
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries, allUpdated: forceAllUpdated)
|
|
|
|
var adjustedDeleteIndices: [ListViewDeleteItem] = []
|
|
let previousCount: Int
|
|
if let fromView = fromView {
|
|
previousCount = fromView.filteredEntries.count
|
|
} else {
|
|
previousCount = 0
|
|
}
|
|
for index in deleteIndices {
|
|
adjustedDeleteIndices.append(ListViewDeleteItem(index: previousCount - 1 - index, directionHint: nil))
|
|
}
|
|
var adjustedIndicesAndItems: [ChatListNodeViewTransitionInsertEntry] = []
|
|
var adjustedUpdateItems: [ChatListNodeViewTransitionUpdateEntry] = []
|
|
let updatedCount = toView.filteredEntries.count
|
|
|
|
var options: ListViewDeleteAndInsertOptions = []
|
|
var maxAnimatedInsertionIndex = -1
|
|
var scrollToItem: ListViewScrollToItem?
|
|
|
|
switch reason {
|
|
case .initial:
|
|
let _ = options.insert(.LowLatency)
|
|
let _ = options.insert(.Synchronous)
|
|
case .interactiveChanges:
|
|
for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) {
|
|
let adjustedIndex = updatedCount - 1 - index
|
|
if adjustedIndex == maxAnimatedInsertionIndex + 1 {
|
|
maxAnimatedInsertionIndex += 1
|
|
}
|
|
}
|
|
|
|
var minTimestamp: Int32?
|
|
var maxTimestamp: Int32?
|
|
for (_, item, _) in indicesAndItems {
|
|
if case .PeerEntry = item, case let .index(index) = item.sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex == nil {
|
|
let timestamp = chatListIndex.messageIndex.timestamp
|
|
|
|
if minTimestamp == nil || timestamp < minTimestamp! {
|
|
minTimestamp = timestamp
|
|
}
|
|
if maxTimestamp == nil || timestamp > maxTimestamp! {
|
|
maxTimestamp = timestamp
|
|
}
|
|
}
|
|
}
|
|
|
|
let _ = options.insert(.AnimateAlpha)
|
|
if !disableAnimations {
|
|
let _ = options.insert(.AnimateInsertion)
|
|
}
|
|
case .reload:
|
|
break
|
|
case .holeChanges:
|
|
break
|
|
}
|
|
|
|
for (index, entry, previousIndex) in indicesAndItems {
|
|
let adjustedIndex = updatedCount - 1 - index
|
|
|
|
let adjustedPrevousIndex: Int?
|
|
if let previousIndex = previousIndex {
|
|
adjustedPrevousIndex = previousCount - 1 - previousIndex
|
|
} else {
|
|
adjustedPrevousIndex = nil
|
|
}
|
|
|
|
var directionHint: ListViewItemOperationDirectionHint?
|
|
if maxAnimatedInsertionIndex >= 0 && adjustedIndex <= maxAnimatedInsertionIndex {
|
|
directionHint = .Down
|
|
}
|
|
|
|
adjustedIndicesAndItems.append(ChatListNodeViewTransitionInsertEntry(index: adjustedIndex, previousIndex: adjustedPrevousIndex, entry: entry, directionHint: directionHint))
|
|
}
|
|
|
|
for (index, entry, previousIndex) in updateIndices {
|
|
let adjustedIndex = updatedCount - 1 - index
|
|
let adjustedPreviousIndex = previousCount - 1 - previousIndex
|
|
|
|
let directionHint: ListViewItemOperationDirectionHint? = nil
|
|
adjustedUpdateItems.append(ChatListNodeViewTransitionUpdateEntry(index: adjustedIndex, previousIndex: adjustedPreviousIndex, entry: entry, directionHint: directionHint))
|
|
}
|
|
|
|
if let scrollPosition = scrollPosition {
|
|
switch scrollPosition {
|
|
case let .index(scrollIndex, position, directionHint, animated):
|
|
var index = toView.filteredEntries.count - 1
|
|
for entry in toView.filteredEntries {
|
|
if entry.sortIndex >= .index(.chatList(scrollIndex)) {
|
|
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
|
|
break
|
|
}
|
|
index -= 1
|
|
}
|
|
|
|
if scrollToItem == nil {
|
|
var index = 0
|
|
for entry in toView.filteredEntries.reversed() {
|
|
if entry.sortIndex < .index(.chatList(scrollIndex)) {
|
|
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
|
|
break
|
|
}
|
|
index += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var fromEmptyView: Bool
|
|
fromEmptyView = false
|
|
var animateCrossfade: Bool
|
|
animateCrossfade = false
|
|
if let fromView = fromView {
|
|
var wasSingleHeader = false
|
|
if fromView.filteredEntries.count == 1, case .HeaderEntry = fromView.filteredEntries[0] {
|
|
wasSingleHeader = true
|
|
}
|
|
var isSingleHeader = false
|
|
if toView.filteredEntries.count == 1, case .HeaderEntry = toView.filteredEntries[0] {
|
|
isSingleHeader = true
|
|
}
|
|
if (wasSingleHeader || isSingleHeader), case .interactiveChanges = reason {
|
|
if wasSingleHeader != isSingleHeader {
|
|
if wasSingleHeader {
|
|
animateCrossfade = true
|
|
options.remove(.AnimateInsertion)
|
|
options.remove(.AnimateAlpha)
|
|
} else {
|
|
let _ = options.insert(.AnimateInsertion)
|
|
}
|
|
}
|
|
} else if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter {
|
|
var updateEmpty = true
|
|
if !fromView.filteredEntries.isEmpty, let fromFilter = fromView.filter, let toFilter = toView.filter, case var .filter(_, _, _, fromData) = fromFilter, case let .filter(_, _, _, toData) = toFilter, fromData.includePeers.pinnedPeers != toData.includePeers.pinnedPeers {
|
|
fromData.includePeers = toData.includePeers
|
|
if fromData == toData {
|
|
options.insert(.AnimateInsertion)
|
|
updateEmpty = false
|
|
}
|
|
}
|
|
|
|
if updateEmpty {
|
|
options.remove(.AnimateInsertion)
|
|
options.remove(.AnimateAlpha)
|
|
fromEmptyView = true
|
|
}
|
|
}
|
|
} else {
|
|
fromEmptyView = true
|
|
}
|
|
|
|
if let fromView = fromView, !fromView.isLoading, toView.isLoading {
|
|
options.remove(.AnimateInsertion)
|
|
options.remove(.AnimateAlpha)
|
|
}
|
|
|
|
var adjustScrollToFirstItem = false
|
|
if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 1 {
|
|
adjustScrollToFirstItem = true
|
|
}
|
|
|
|
subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: nil, adjustScrollToFirstItem: adjustScrollToFirstItem, animateCrossfade: animateCrossfade))
|
|
subscriber.putCompletion()
|
|
|
|
return EmptyDisposable
|
|
}
|
|
}
|