import Foundation import UIKit import Display import AsyncDisplayKit import AnimatedStickerNode import SwiftSignalKit import AppBundle class WalletInfoEmptyItem: ListViewItem { let theme: WalletTheme let strings: WalletStrings let address: String let loading: Bool let displayAddressContextMenu: (ASDisplayNode, CGRect) -> Void let selectable: Bool = false init(theme: WalletTheme, strings: WalletStrings, address: String, loading: Bool, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) { self.theme = theme self.strings = strings self.address = address self.loading = loading self.displayAddressContextMenu = displayAddressContextMenu } 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 = WalletInfoEmptyItemNode() node.insets = UIEdgeInsets() 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 WalletInfoEmptyItemNode) if let nodeValue = node() as? WalletInfoEmptyItemNode { let layout = nodeValue.asyncLayout() async { let (nodeLayout, apply) = layout(self, params) Queue.mainQueue().async { completion(nodeLayout, { _ in apply() }) } } } } } } final class WalletInfoEmptyItemNode: ListViewItemNode { private let offsetContainer: ASDisplayNode private let animationNode: AnimatedStickerNode private let titleNode: TextNode private let textNode: TextNode private let addressNode: TextNode private var item: WalletInfoEmptyItem? init() { self.offsetContainer = ASDisplayNode() self.animationNode = AnimatedStickerNode() self.titleNode = TextNode() self.titleNode.displaysAsynchronously = false self.textNode = TextNode() self.addressNode = TextNode() super.init(layerBacked: false) self.wantsTrailingItemSpaceUpdates = true self.offsetContainer.addSubnode(self.animationNode) self.offsetContainer.addSubnode(self.titleNode) self.offsetContainer.addSubnode(self.textNode) self.offsetContainer.addSubnode(self.addressNode) self.addSubnode(self.offsetContainer) } override func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) recognizer.tapActionAtPoint = { point in return .waitForSingleTap } self.addressNode.view.addGestureRecognizer(recognizer) } @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { switch gesture { case .longTap: self.item?.displayAddressContextMenu(self, self.addressNode.frame) default: break } } default: break } } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { let layout = self.asyncLayout() let (_, apply) = layout(item as! WalletInfoEmptyItem, params) apply() } func asyncLayout() -> (_ item: WalletInfoEmptyItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeAddressLayout = TextNode.asyncLayout(self.addressNode) return { [weak self] item, params in var iconSpacing: CGFloat = 5.0 var titleSpacing: CGFloat = 19.0 var iconSize = CGSize(width: 140.0, height: 140.0) let contentVerticalOrigin: CGFloat = 10.0 let sideInset: CGFloat = 16.0 let iconOffset = CGPoint() let title = item.strings.Wallet_Info_WalletCreated let text = item.strings.Wallet_Info_Address let textColor = item.theme.list.itemPrimaryTextColor let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.bold(32.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: Font.regular(16.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets())) var addressString = item.address addressString.insert("\n", at: addressString.index(addressString.startIndex, offsetBy: addressString.count / 2)) let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: addressString, font: Font.monospace(16.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets())) let availableHeight = params.availableHeight - contentVerticalOrigin * 2.0 let rawContentHeight: CGFloat = iconSize.height + titleLayout.size.height + textLayout.size.height + addressLayout.size.height let contentSpacing = iconSpacing + titleSpacing + titleSpacing let contentSpacingFactor = max(0.2, min(1.0, (availableHeight - rawContentHeight) / contentSpacing)) if contentSpacingFactor < 0.25 { iconSize = CGSize(width: 90.0, height: 90.0) } iconSpacing = floor(iconSpacing * contentSpacingFactor) titleSpacing = floor(titleSpacing * contentSpacingFactor) let iconFrame = CGRect(origin: CGPoint(x: floor((params.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y) let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.size.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleLayout.size) let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textLayout.size) let addressFrame = CGRect(origin: CGPoint(x: floor((params.width - addressLayout.size.width) / 2.0), y: textFrame.maxY + titleSpacing), size: addressLayout.size) let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: (item.loading ? iconFrame.maxY : addressFrame.maxY) + contentVerticalOrigin), insets: UIEdgeInsets()) return (layout, { guard let strongSelf = self else { return } if strongSelf.item?.loading != item.loading { if item.loading { if let path = getAppBundle().path(forResource: "WalletInitializing", ofType: "tgs") { strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .loop, mode: .direct) strongSelf.animationNode.visibility = true } } else { if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") { strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .once, mode: .direct) strongSelf.animationNode.visibility = true } } } strongSelf.item = item strongSelf.animationNode.updateLayout(size: iconSize) strongSelf.offsetContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) let _ = titleApply() let _ = textApply() let _ = addressApply() let transition: ContainedViewLayoutTransition = .immediate transition.updateFrameAdditive(node: strongSelf.animationNode, frame: iconFrame) strongSelf.animationNode.updateLayout(size: iconFrame.size) transition.updateFrameAdditive(node: strongSelf.animationNode, frame: iconFrame) transition.updateFrameAdditive(node: strongSelf.titleNode, frame: titleFrame) transition.updateFrameAdditive(node: strongSelf.textNode, frame: textFrame) transition.updateFrameAdditive(node: strongSelf.addressNode, frame: addressFrame) strongSelf.titleNode.isHidden = item.loading strongSelf.textNode.isHidden = item.loading strongSelf.addressNode.isHidden = item.loading strongSelf.contentSize = layout.contentSize strongSelf.insets = layout.insets }) } } override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) { if height.isLessThanOrEqualTo(0.0) { transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size)) } else { transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels(height / 2.0)), size: self.offsetContainer.bounds.size)) } } }