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?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ChatListArchiveInfoItemNode() node.insets = ChatListItemNode.insets(first: false, last: false, firstWithHeader: false) node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem) Queue.mainQueue().async { completion(node, { return (nil, { _ in }) }) } } } 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 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 = "This is your archive" let text = "Chats with enabled notifications get unarchived when new messages arrive" 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 = 90.0 let bottomContentInset: CGFloat = 64.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.iconNodeBase.image == nil { if index == 0 { strongSelf.iconNodeBase.image = UIImage(bundleImageName: "Chat List/Archive/Intro1Base") } else { strongSelf.iconNodeBase.image = UIImage(bundleImageName: "Chat List/Archive/Intro2Base") } 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 = 115.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) 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 } 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 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.list.itemAccentColor } 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) 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) strongSelf.addSubnode(headerNode) strongSelf.headerNode = headerNode } if let headerNode = strongSelf.headerNode { headerNode.frame = CGRect(origin: CGPoint(), size: CGSize()) } 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) } } }