This commit is contained in:
Ilya Laktyushin 2024-04-16 18:43:14 +04:00
parent 7357c59707
commit 156c84e4f7

View File

@ -1,322 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import TelegramUIPreferences
import TelegramPresentationData
import LegacyComponents
import ItemListUI
import PresentationDataUtils
import EmojiTextAttachmentView
import TextFormat
import AccountContext
import UIKitRuntimeUtils
final class CpmSliderItem: ListViewItem, ItemListItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let value: Int32
let animatedEmoji: TelegramMediaFile?
let sectionId: ItemListSectionId
let updated: (Int32) -> Void
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, animatedEmoji: TelegramMediaFile?, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
self.value = value
self.animatedEmoji = animatedEmoji
self.sectionId = sectionId
self.updated = updated
}
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 = CpmSliderItemNode()
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? CpmSliderItemNode {
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()
})
}
}
}
}
}
}
private let allowedValues: [Int32] = [1, 2, 3, 4, 5]
class CpmSliderItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let minTextNode: TextNode
private let maxTextNode: TextNode
private let textNode: TextNode
private var sliderView: TGPhotoEditorSliderView?
private var animatedEmojiLayer: InlineStickerItemLayer?
private var maxAnimatedEmojiLayer: InlineStickerItemLayer?
private var item: CpmSliderItem?
private var layoutParams: ListViewItemLayoutParams?
private var reportedValue: Int32?
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.minTextNode = TextNode()
self.minTextNode.isUserInteractionEnabled = false
self.minTextNode.displaysAsynchronously = false
self.maxTextNode = TextNode()
self.maxTextNode.isUserInteractionEnabled = false
self.maxTextNode.displaysAsynchronously = false
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.textNode)
self.addSubnode(self.minTextNode)
self.addSubnode(self.maxTextNode)
}
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true
let sliderView = TGPhotoEditorSliderView()
sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 2.0
sliderView.lineSize = 4.0
sliderView.dotSize = 5.0
sliderView.minimumValue = 0.0
sliderView.maximumValue = 1.0
sliderView.startValue = 0.0
sliderView.displayEdges = true
sliderView.disablesInteractiveTransitionGestureRecognizer = true
if let item = self.item, let params = self.layoutParams {
sliderView.value = CGFloat(item.value) / 50.0
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
sliderView.trackColor = item.theme.list.itemAccentColor
sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme)
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
}
self.view.addSubview(sliderView)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
self.sliderView = sliderView
}
func asyncLayout() -> (_ item: CpmSliderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeMinTextLayout = TextNode.asyncLayout(self.minTextNode)
let makeMaxTextLayout = TextNode.asyncLayout(self.maxTextNode)
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
//TODO:localize
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.value == 0 ? "No Ads" : "\(item.value) CPM", font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (minTextLayout, minTextApply) = makeMinTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No Ads", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (maxTextLayout, maxTextApply) = makeMaxTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "50 CPM", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
contentSize = CGSize(width: params.width, height: 88.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.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.insertSubnode(strongSelf.maskNode, at: 3)
}
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 = 0.0
bottomStripeOffset = -separatorHeight
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
bottomStripeOffset = 0.0
}
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 _ = textApply()
let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: 12.0), size: textLayout.size)
strongSelf.textNode.frame = textFrame
if let animatedEmoji = item.animatedEmoji {
let itemSize = floorToScreenPixels(17.0 * 20.0 / 17.0)
var itemFrame = CGRect(origin: CGPoint(x: textFrame.minX - itemSize / 2.0 - 1.0, y: textFrame.midY), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
let itemLayer: InlineStickerItemLayer
if let current = strongSelf.animatedEmojiLayer {
itemLayer = current
} else {
let pointSize = floor(itemSize * 1.3)
itemLayer = InlineStickerItemLayer(context: item.context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
strongSelf.animatedEmojiLayer = itemLayer
strongSelf.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = true
}
itemLayer.frame = itemFrame
}
let _ = minTextApply()
strongSelf.minTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: 16.0), size: minTextLayout.size)
let _ = maxTextApply()
let maxTextFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 16.0 - maxTextLayout.size.width, y: 16.0), size: maxTextLayout.size)
strongSelf.maxTextNode.frame = maxTextFrame
if let animatedEmoji = item.animatedEmoji {
let itemSize = floorToScreenPixels(13.0 * 20.0 / 17.0)
var itemFrame = CGRect(origin: CGPoint(x: maxTextFrame.minX - itemSize / 2.0 - 1.0, y: maxTextFrame.midY), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
let itemLayer: InlineStickerItemLayer
if let current = strongSelf.maxAnimatedEmojiLayer {
itemLayer = current
} else {
let pointSize = floor(itemSize * 1.3)
itemLayer = InlineStickerItemLayer(context: item.context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
strongSelf.maxAnimatedEmojiLayer = itemLayer
strongSelf.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = true
if let filter = makeMonochromeFilter() {
filter.setValue([1.0, 1.0, 1.0, 1.0] as [NSNumber], forKey: "inputColor")
filter.setValue(1.0 as NSNumber, forKey: "inputAmount")
itemLayer.filters = [filter]
}
}
itemLayer.frame = itemFrame
}
if let sliderView = strongSelf.sliderView {
if themeUpdated {
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
sliderView.trackColor = item.theme.list.itemAccentColor
sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme)
}
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
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)
}
@objc func sliderValueChanged() {
guard let item = self.item, let sliderView = self.sliderView else {
return
}
item.updated(Int32(sliderView.value * 50.0))
}
}