mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
271 lines
14 KiB
Swift
271 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Postbox
|
|
import Display
|
|
import SwiftSignalKit
|
|
|
|
class ChatListArchiveInfoItem: ListViewItem {
|
|
let theme: PresentationTheme
|
|
let strings: PresentationStrings
|
|
|
|
let selectable: Bool = false
|
|
|
|
init(theme: PresentationTheme, strings: PresentationStrings) {
|
|
self.theme = theme
|
|
self.strings = strings
|
|
}
|
|
|
|
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 = ChatListArchiveInfoItemNode()
|
|
|
|
let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
|
|
|
|
node.insets = nodeLayout.insets
|
|
node.contentSize = nodeLayout.contentSize
|
|
|
|
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 {
|
|
assert(node() is ChatListArchiveInfoItemNode)
|
|
if let nodeValue = node() as? ChatListArchiveInfoItemNode {
|
|
|
|
let layout = nodeValue.asyncLayout()
|
|
async {
|
|
let (nodeLayout, apply) = layout(self, params, nextItem == nil)
|
|
Queue.mainQueue().async {
|
|
completion(nodeLayout, { _ in
|
|
apply()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private let separatorHeight = 1.0 / UIScreen.main.scale
|
|
|
|
private let titleFont = Font.regular(20.0)
|
|
private let textFont = Font.regular(15.0)
|
|
|
|
private final class InfoPageNode: ASDisplayNode {
|
|
private let iconNodeBase: ASImageNode
|
|
private let iconNodeContent: ASImageNode
|
|
private let titleNode: TextNode
|
|
private let textNode: TextNode
|
|
|
|
private var theme: PresentationTheme?
|
|
|
|
override init() {
|
|
self.iconNodeBase = ASImageNode()
|
|
self.iconNodeBase.displaysAsynchronously = false
|
|
self.iconNodeBase.displayWithoutProcessing = true
|
|
|
|
self.iconNodeContent = ASImageNode()
|
|
self.iconNodeContent.displaysAsynchronously = false
|
|
self.iconNodeContent.displayWithoutProcessing = true
|
|
|
|
self.titleNode = TextNode()
|
|
self.titleNode.displaysAsynchronously = false
|
|
|
|
self.textNode = TextNode()
|
|
self.textNode.displaysAsynchronously = false
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.iconNodeBase)
|
|
self.addSubnode(self.iconNodeContent)
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.textNode)
|
|
}
|
|
|
|
func asyncLayout() -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ width: CGFloat, _ index: Int) -> (CGFloat, () -> Void) {
|
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
|
return { [weak self] theme, strings, width, index in
|
|
let title: String
|
|
let text: String
|
|
if index == 0 {
|
|
title = strings.ArchivedChats_IntroTitle1
|
|
text = strings.ArchivedChats_IntroText1
|
|
} else if index == 1 {
|
|
title = strings.ArchivedChats_IntroTitle2
|
|
text = strings.ArchivedChats_IntroText2
|
|
} else {
|
|
title = strings.ArchivedChats_IntroTitle3
|
|
text = strings.ArchivedChats_IntroText3
|
|
}
|
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: nil), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: min(300.0, width - 16.0), height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: nil), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: min(300.0, width - 16.0), height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
|
|
let topContentInset: CGFloat = 98.0
|
|
let bottomContentInset: CGFloat = 64.0 + 28.0
|
|
let textSpacing: CGFloat = 6.0
|
|
|
|
let contentHeight = topContentInset + titleLayout.size.height + textSpacing + textLayout.size.height + bottomContentInset
|
|
|
|
return (contentHeight, {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
if strongSelf.theme !== theme {
|
|
strongSelf.theme = theme
|
|
if index == 0 {
|
|
strongSelf.iconNodeBase.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/Intro1Base"), color: theme.list.itemPrimaryTextColor)
|
|
} else {
|
|
strongSelf.iconNodeBase.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/Intro2Base"), color: theme.list.itemPrimaryTextColor)
|
|
}
|
|
if index == 0 {
|
|
strongSelf.iconNodeContent.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/Intro1Content"), color: theme.chatList.unreadBadgeActiveBackgroundColor)
|
|
} else if index == 1 {
|
|
strongSelf.iconNodeContent.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/Intro2Content"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
|
|
} else {
|
|
strongSelf.iconNodeContent.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/Intro3Content"), color: theme.chatList.unreadBadgeActiveBackgroundColor)
|
|
}
|
|
}
|
|
|
|
let topIconInset: CGFloat = 110.0
|
|
|
|
if let baseImage = strongSelf.iconNodeBase.image, let contentImage = strongSelf.iconNodeContent.image {
|
|
strongSelf.iconNodeBase.frame = CGRect(origin: CGPoint(x: floor((width - baseImage.size.width) / 2.0), y: floor((topIconInset - baseImage.size.height) / 2.0)), size: baseImage.size)
|
|
strongSelf.iconNodeContent.frame = CGRect(origin: CGPoint(x: floor((width - contentImage.size.width) / 2.0), y: floor((topIconInset - contentImage.size.height) / 2.0)), size: contentImage.size)
|
|
}
|
|
|
|
let _ = titleApply()
|
|
let _ = textApply()
|
|
|
|
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleLayout.size.width) / 2.0), y: topContentInset), size: titleLayout.size)
|
|
strongSelf.titleNode.frame = titleFrame
|
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((width - textLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: textLayout.size)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
class ChatListArchiveInfoItemNode: ListViewItemNode, UIScrollViewDelegate {
|
|
private var item: ChatListArchiveInfoItem?
|
|
|
|
private let scrollNode: ASScrollNode
|
|
private let pageControlNode: PageControlNode
|
|
private var headerNode: ListSectionHeaderNode?
|
|
private let infoPageNodes: [InfoPageNode]
|
|
|
|
required init() {
|
|
self.scrollNode = ASScrollNode()
|
|
|
|
self.pageControlNode = PageControlNode(dotSize: 7.0, dotSpacing: 9.0, dotColor: .blue, inactiveDotColor: .gray)
|
|
|
|
self.infoPageNodes = (0 ..< 3).map({ _ in InfoPageNode() })
|
|
self.pageControlNode.pagesCount = self.infoPageNodes.count
|
|
|
|
super.init(layerBacked: false, dynamicBounce: false)
|
|
|
|
self.addSubnode(self.scrollNode)
|
|
self.infoPageNodes.forEach(self.scrollNode.addSubnode)
|
|
|
|
self.addSubnode(self.pageControlNode)
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
|
|
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
|
self.scrollNode.view.isPagingEnabled = true
|
|
self.scrollNode.view.delegate = self
|
|
self.pageControlNode.setPage(0.0)
|
|
}
|
|
|
|
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
|
let layout = self.asyncLayout()
|
|
let (_, apply) = layout(item as! ChatListArchiveInfoItem, params, nextItem == nil)
|
|
apply()
|
|
}
|
|
|
|
func asyncLayout() -> (_ item: ChatListArchiveInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
|
let previousItem = self.item
|
|
|
|
let makeInfoPageLayouts = self.infoPageNodes.map({ $0.asyncLayout() })
|
|
|
|
return { item, params, last in
|
|
let baseWidth = params.width - params.leftInset - params.rightInset
|
|
let bottomInset: CGFloat = 22.0 + 28.0
|
|
|
|
let themeUpdated = previousItem?.theme !== item.theme
|
|
|
|
var infoPageLayoutsAndApply: [(CGFloat, () -> Void)] = []
|
|
var maxHeight: CGFloat = 0.0
|
|
for i in 0 ..< makeInfoPageLayouts.count {
|
|
let sizeAndApply = makeInfoPageLayouts[i](item.theme, item.strings, baseWidth, i)
|
|
maxHeight = max(maxHeight, sizeAndApply.0)
|
|
infoPageLayoutsAndApply.append(sizeAndApply)
|
|
}
|
|
|
|
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: maxHeight), insets: UIEdgeInsets())
|
|
|
|
return (layout, { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.item = item
|
|
|
|
if themeUpdated {
|
|
strongSelf.pageControlNode.dotColor = item.theme.chatList.unreadBadgeActiveBackgroundColor
|
|
strongSelf.pageControlNode.inactiveDotColor = item.theme.list.pageIndicatorInactiveColor
|
|
}
|
|
|
|
let resetOffset = !strongSelf.scrollNode.frame.width.isEqual(to: baseWidth)
|
|
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: baseWidth, height: layout.contentSize.height))
|
|
strongSelf.scrollNode.view.contentSize = CGSize(width: baseWidth * CGFloat(infoPageLayoutsAndApply.count), height: layout.contentSize.height)
|
|
if resetOffset {
|
|
strongSelf.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: 0.0)
|
|
}
|
|
for i in 0 ..< infoPageLayoutsAndApply.count {
|
|
strongSelf.infoPageNodes[i].frame = CGRect(origin: CGPoint(x: baseWidth * CGFloat(i), y: 0.0), size: CGSize(width: baseWidth, height: layout.contentSize.height))
|
|
infoPageLayoutsAndApply[i].1()
|
|
}
|
|
|
|
let pageControlSize = strongSelf.pageControlNode.measure(CGSize(width: baseWidth, height: 100.0))
|
|
strongSelf.pageControlNode.frame = CGRect(origin: CGPoint(x: floor((params.width - pageControlSize.width) / 2.0), y: layout.contentSize.height - bottomInset - pageControlSize.height), size: pageControlSize)
|
|
|
|
if strongSelf.headerNode == nil {
|
|
let headerNode = ListSectionHeaderNode(theme: item.theme)
|
|
headerNode.title = item.strings.ChatList_ArchivedChatsTitle.uppercased()
|
|
strongSelf.addSubnode(headerNode)
|
|
strongSelf.headerNode = headerNode
|
|
}
|
|
|
|
if let headerNode = strongSelf.headerNode {
|
|
if themeUpdated {
|
|
headerNode.updateTheme(theme: item.theme)
|
|
}
|
|
headerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.contentSize.height - 28.0), size: CGSize(width: params.width, height: 28.0))
|
|
headerNode.updateLayout(size: CGSize(width: params.width, height: 28.0), leftInset: params.leftInset, rightInset: params.rightInset)
|
|
}
|
|
|
|
strongSelf.contentSize = layout.contentSize
|
|
strongSelf.insets = layout.insets
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
let bounds = scrollView.bounds
|
|
if !bounds.width.isZero {
|
|
self.pageControlNode.setPage(scrollView.contentOffset.x / bounds.width)
|
|
}
|
|
}
|
|
}
|