mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
308 lines
14 KiB
Swift
308 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import ItemListUI
|
|
import PresentationDataUtils
|
|
import HorizontalPeerItem
|
|
import AccountContext
|
|
import MergeLists
|
|
|
|
private struct PeersEntry: Comparable, Identifiable {
|
|
let index: Int
|
|
let peer: EnginePeer
|
|
let theme: PresentationTheme
|
|
let strings: PresentationStrings
|
|
|
|
var stableId: EnginePeer.Id {
|
|
return self.peer.id
|
|
}
|
|
|
|
static func ==(lhs: PeersEntry, rhs: PeersEntry) -> Bool {
|
|
if lhs.index != rhs.index {
|
|
return false
|
|
}
|
|
if lhs.peer != rhs.peer {
|
|
return false
|
|
}
|
|
if lhs.theme !== rhs.theme {
|
|
return false
|
|
}
|
|
if lhs.strings !== rhs.strings {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
static func <(lhs: PeersEntry, rhs: PeersEntry) -> Bool {
|
|
return lhs.index < rhs.index
|
|
}
|
|
|
|
func item(context: AccountContext) -> ListViewItem {
|
|
return HorizontalPeerItem(
|
|
theme: self.theme,
|
|
strings: self.strings,
|
|
mode: .list(compact: true),
|
|
accountPeerId: context.account.peerId,
|
|
postbox: context.account.postbox,
|
|
network: context.account.network,
|
|
energyUsageSettings: context.sharedContext.energyUsageSettings,
|
|
contentSettings: context.currentContentSettings.with { $0 },
|
|
animationCache: context.animationCache,
|
|
animationRenderer: context.animationRenderer,
|
|
resolveInlineStickers: context.engine.stickers.resolveInlineStickers,
|
|
peer: self.peer,
|
|
presence: nil,
|
|
unreadBadge: nil,
|
|
action: { _ in },
|
|
contextAction: nil,
|
|
isPeerSelected: { _ in return false },
|
|
customWidth: nil
|
|
)
|
|
}
|
|
}
|
|
|
|
private struct DeleteAccountPeersItemNodeTransition {
|
|
let deletions: [ListViewDeleteItem]
|
|
let insertions: [ListViewInsertItem]
|
|
let updates: [ListViewUpdateItem]
|
|
let firstTime: Bool
|
|
let animated: Bool
|
|
}
|
|
|
|
private func preparedPeersTransition(context: AccountContext, from fromEntries: [PeersEntry], to toEntries: [PeersEntry], firstTime: Bool, animated: Bool) -> DeleteAccountPeersItemNodeTransition {
|
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
|
|
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context), directionHint: .Down) }
|
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context), directionHint: nil) }
|
|
|
|
return DeleteAccountPeersItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated)
|
|
}
|
|
|
|
class DeleteAccountPeersItem: ListViewItem, ItemListItem {
|
|
var sectionId: ItemListSectionId
|
|
|
|
let context: AccountContext
|
|
let theme: PresentationTheme
|
|
let strings: PresentationStrings
|
|
let peers: [EnginePeer]
|
|
|
|
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peers: [EnginePeer], sectionId: ItemListSectionId) {
|
|
self.context = context
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.peers = peers
|
|
self.sectionId = sectionId
|
|
}
|
|
|
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
|
async {
|
|
let node = DeleteAccountPeersItemNode()
|
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
|
|
node.contentSize = layout.contentSize
|
|
node.insets = layout.insets
|
|
|
|
Queue.mainQueue().async {
|
|
completion(node, {
|
|
return (nil, { _ in apply() })
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
|
Queue.mainQueue().async {
|
|
if let nodeValue = node() as? DeleteAccountPeersItemNode {
|
|
let makeLayout = nodeValue.asyncLayout()
|
|
|
|
async {
|
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
Queue.mainQueue().async {
|
|
completion(layout, { _ in
|
|
apply()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class DeleteAccountPeersItemNode: ListViewItemNode, ItemListItemNode {
|
|
private let backgroundNode: ASDisplayNode
|
|
private let topStripeNode: ASDisplayNode
|
|
private let bottomStripeNode: ASDisplayNode
|
|
private let maskNode: ASImageNode
|
|
|
|
private let dataPromise = Promise<(AccountContext, [EnginePeer], PresentationTheme, PresentationStrings)>()
|
|
private var disposable: Disposable?
|
|
|
|
private var item: DeleteAccountPeersItem?
|
|
private var layoutParams: ListViewItemLayoutParams?
|
|
|
|
private let listView: ListView
|
|
private var queuedTransitions: [DeleteAccountPeersItemNodeTransition] = []
|
|
|
|
var tag: ItemListItemTag? {
|
|
return self.item?.tag
|
|
}
|
|
|
|
init() {
|
|
self.backgroundNode = ASDisplayNode()
|
|
self.backgroundNode.isLayerBacked = true
|
|
|
|
self.topStripeNode = ASDisplayNode()
|
|
self.topStripeNode.isLayerBacked = true
|
|
|
|
self.bottomStripeNode = ASDisplayNode()
|
|
self.bottomStripeNode.isLayerBacked = true
|
|
|
|
self.maskNode = ASImageNode()
|
|
self.maskNode.isUserInteractionEnabled = false
|
|
|
|
self.listView = ListView()
|
|
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
|
|
|
super.init(layerBacked: false, dynamicBounce: false)
|
|
|
|
self.addSubnode(self.listView)
|
|
|
|
let previous: Atomic<[PeersEntry]> = Atomic(value: [])
|
|
let firstTime:Atomic<Bool> = Atomic(value: true)
|
|
|
|
self.disposable = (self.dataPromise.get() |> deliverOnMainQueue).start(next: { [weak self] data in
|
|
if let strongSelf = self {
|
|
let (context, peers, theme, strings) = data
|
|
|
|
var entries: [PeersEntry] = []
|
|
for peer in peers {
|
|
entries.append(PeersEntry(index: entries.count, peer: peer, theme: theme, strings: strings))
|
|
}
|
|
|
|
let animated = !firstTime.swap(false)
|
|
|
|
let transition = preparedPeersTransition(context: context, from: previous.swap(entries), to: entries, firstTime: !animated, animated: animated)
|
|
strongSelf.enqueueTransition(transition)
|
|
}
|
|
})
|
|
}
|
|
|
|
deinit {
|
|
self.disposable?.dispose()
|
|
}
|
|
|
|
private func enqueueTransition(_ transition: DeleteAccountPeersItemNodeTransition) {
|
|
self.queuedTransitions.append(transition)
|
|
self.dequeueTransitions()
|
|
}
|
|
|
|
private func dequeueTransitions() {
|
|
while !self.queuedTransitions.isEmpty {
|
|
let transition = self.queuedTransitions.removeFirst()
|
|
|
|
var options = ListViewDeleteAndInsertOptions()
|
|
if transition.firstTime {
|
|
options.insert(.PreferSynchronousResourceLoading)
|
|
options.insert(.PreferSynchronousDrawing)
|
|
options.insert(.Synchronous)
|
|
options.insert(.LowLatency)
|
|
} else if transition.animated {
|
|
options.insert(.AnimateInsertion)
|
|
}
|
|
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { _ in })
|
|
}
|
|
}
|
|
|
|
func asyncLayout() -> (_ item: DeleteAccountPeersItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
|
return { item, params, neighbors in
|
|
let contentSize: CGSize
|
|
var insets: UIEdgeInsets
|
|
let separatorHeight = UIScreenPixel
|
|
|
|
contentSize = CGSize(width: params.width, height: 109.0)
|
|
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
|
|
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
|
let layoutSize = layout.size
|
|
|
|
return (layout, { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.item = item
|
|
strongSelf.layoutParams = params
|
|
|
|
strongSelf.dataPromise.set(.single((item.context, item.peers, item.theme, item.strings)))
|
|
|
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
|
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
|
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
|
|
|
if strongSelf.backgroundNode.supernode == nil {
|
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
}
|
|
if strongSelf.topStripeNode.supernode == nil {
|
|
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
|
}
|
|
if strongSelf.bottomStripeNode.supernode == nil {
|
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
|
}
|
|
if strongSelf.maskNode.supernode == nil {
|
|
strongSelf.addSubnode(strongSelf.maskNode)
|
|
}
|
|
|
|
let hasCorners = itemListHasRoundedBlockLayout(params)
|
|
var hasTopCorners = false
|
|
var hasBottomCorners = false
|
|
switch neighbors.top {
|
|
case .sameSection(false):
|
|
strongSelf.topStripeNode.isHidden = true
|
|
default:
|
|
hasTopCorners = true
|
|
strongSelf.topStripeNode.isHidden = hasCorners
|
|
}
|
|
let bottomStripeInset: CGFloat
|
|
let bottomStripeOffset: CGFloat
|
|
switch neighbors.bottom {
|
|
case .sameSection(false):
|
|
bottomStripeInset = params.leftInset + 16.0
|
|
bottomStripeOffset = -separatorHeight
|
|
strongSelf.bottomStripeNode.isHidden = false
|
|
default:
|
|
bottomStripeInset = 0.0
|
|
bottomStripeOffset = 0.0
|
|
hasBottomCorners = true
|
|
strongSelf.bottomStripeNode.isHidden = hasCorners
|
|
}
|
|
|
|
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
|
|
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
|
|
|
let listInsets = UIEdgeInsets(top: params.leftInset, left: 0.0, bottom: params.rightInset, right: 0.0)
|
|
strongSelf.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: params.width)
|
|
strongSelf.listView.position = CGPoint(x: params.width / 2.0, y: contentSize.height / 2.0)
|
|
strongSelf.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: params.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
|
|
|
strongSelf.dequeueTransitions()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
}
|
|
|
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
|
}
|
|
}
|