Swiftgram/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift
2024-02-09 23:04:16 +04:00

223 lines
9.4 KiB
Swift

import Foundation
import Postbox
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
}
enum ChatListNodeViewScrollPosition {
case index(index: ChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
}
func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, previewing: Bool, disableAnimations: Bool, account: Account, 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
}
}