mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
259 lines
12 KiB
Swift
259 lines
12 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import TelegramCore
|
|
import Display
|
|
|
|
private let titleFont = Font.medium(16.0)
|
|
private let middleImage = messageBubbleActionButtonImage(color: UIColor(0x596E89), position: .middle)
|
|
private let bottomLeftImage = messageBubbleActionButtonImage(color: UIColor(0x596E89), position: .bottomLeft)
|
|
private let bottomRightImage = messageBubbleActionButtonImage(color: UIColor(0x596E89), position: .bottomRight)
|
|
private let bottomSingleImage = messageBubbleActionButtonImage(color: UIColor(0x596E89), position: .bottomSingle)
|
|
|
|
private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|
private let backgroundNode: ASImageNode
|
|
private var titleNode: TextNode?
|
|
private var buttonView: HighlightTrackingButton?
|
|
|
|
private var button: ReplyMarkupButton?
|
|
var pressed: ((ReplyMarkupButton) -> Void)?
|
|
|
|
override init() {
|
|
self.backgroundNode = ASImageNode()
|
|
self.backgroundNode.displayWithoutProcessing = true
|
|
self.backgroundNode.displaysAsynchronously = false
|
|
self.backgroundNode.isLayerBacked = true
|
|
self.backgroundNode.alpha = 0.35
|
|
self.backgroundNode.isUserInteractionEnabled = false
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.backgroundNode)
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
let buttonView = HighlightTrackingButton(frame: self.bounds)
|
|
buttonView.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
|
|
self.buttonView = buttonView
|
|
self.view.addSubview(buttonView)
|
|
buttonView.highligthedChanged = { [weak self] highlighted in
|
|
if let strongSelf = self {
|
|
if highlighted {
|
|
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
|
strongSelf.backgroundNode.alpha = 0.55
|
|
} else {
|
|
strongSelf.backgroundNode.alpha = 0.35
|
|
strongSelf.backgroundNode.layer.animateAlpha(from: 0.55, to: 0.35, duration: 0.2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func buttonPressed() {
|
|
if let button = self.button, let pressed = self.pressed {
|
|
pressed(button)
|
|
}
|
|
}
|
|
|
|
class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ button: ReplyMarkupButton, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, () -> ChatMessageActionButtonNode))) {
|
|
let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
|
|
|
return { button, constrainedWidth, position in
|
|
let sideInset: CGFloat = 8.0
|
|
let minimumSideInset: CGFloat = 4.0
|
|
let (titleSize, titleApply) = titleLayout(NSAttributedString(string: button.title, font: titleFont, textColor: .white), nil, 1, .end, CGSize(width: max(1.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), .natural, nil)
|
|
|
|
let backgroundImage: UIImage
|
|
switch position {
|
|
case .middle:
|
|
backgroundImage = middleImage
|
|
case .bottomLeft:
|
|
backgroundImage = bottomLeftImage
|
|
case .bottomRight:
|
|
backgroundImage = bottomRightImage
|
|
case .bottomSingle:
|
|
backgroundImage = bottomSingleImage
|
|
}
|
|
|
|
return (titleSize.size.width + sideInset + sideInset, { width in
|
|
return (CGSize(width: width, height: 42.0), {
|
|
let node: ChatMessageActionButtonNode
|
|
if let maybeNode = maybeNode {
|
|
node = maybeNode
|
|
} else {
|
|
node = ChatMessageActionButtonNode()
|
|
}
|
|
|
|
node.button = button
|
|
|
|
node.backgroundNode.image = backgroundImage
|
|
node.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0))
|
|
|
|
let titleNode = titleApply()
|
|
if node.titleNode !== titleNode {
|
|
node.titleNode = titleNode
|
|
node.addSubnode(titleNode)
|
|
titleNode.isUserInteractionEnabled = false
|
|
}
|
|
titleNode.frame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size)
|
|
|
|
node.buttonView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
|
|
|
|
return node
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|
private var buttonNodes: [ChatMessageActionButtonNode] = []
|
|
|
|
private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)?
|
|
var buttonPressed: ((ReplyMarkupButton) -> Void)?
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
self.buttonPressedWrapper = { [weak self] button in
|
|
if let buttonPressed = self?.buttonPressed {
|
|
buttonPressed(button)
|
|
}
|
|
}
|
|
}
|
|
|
|
//(_ item: ChatMessageItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (maxWidth: CGFloat, layout: (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void)))
|
|
|
|
class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ replyMarkup: ReplyMarkupMessageAttribute, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animated: Bool) -> ChatMessageActionButtonsNode)) {
|
|
let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? []
|
|
|
|
return { replyMarkup, constrainedWidth in
|
|
let buttonHeight: CGFloat = 42.0
|
|
let buttonSpacing: CGFloat = 4.0
|
|
|
|
var overallMinimumRowWidth: CGFloat = 0.0
|
|
|
|
var finalizeRowLayouts: [[((CGFloat) -> (CGSize, () -> ChatMessageActionButtonNode))]] = []
|
|
|
|
var rowIndex = 0
|
|
var buttonIndex = 0
|
|
for row in replyMarkup.rows {
|
|
var maximumRowButtonWidth: CGFloat = 0.0
|
|
let maximumButtonWidth: CGFloat = max(1.0, floor((constrainedWidth - CGFloat(max(0, row.buttons.count - 1)) * buttonSpacing) / CGFloat(row.buttons.count)))
|
|
var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, () -> ChatMessageActionButtonNode))] = []
|
|
var rowButtonIndex = 0
|
|
for button in row.buttons {
|
|
let buttonPosition: MessageBubbleActionButtonPosition
|
|
if rowIndex == replyMarkup.rows.count - 1 {
|
|
if row.buttons.count == 1 {
|
|
buttonPosition = .bottomSingle
|
|
} else if rowButtonIndex == 0 {
|
|
buttonPosition = .bottomLeft
|
|
} else if rowButtonIndex == row.buttons.count - 1 {
|
|
buttonPosition = .bottomRight
|
|
} else {
|
|
buttonPosition = .middle
|
|
}
|
|
} else {
|
|
buttonPosition = .middle
|
|
}
|
|
|
|
let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, () -> ChatMessageActionButtonNode)))
|
|
if buttonIndex < currentButtonLayouts.count {
|
|
prepareButtonLayout = currentButtonLayouts[buttonIndex](button, maximumButtonWidth, buttonPosition)
|
|
} else {
|
|
prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(button, maximumButtonWidth, buttonPosition)
|
|
}
|
|
|
|
maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth)
|
|
finalizeRowButtonLayouts.append(prepareButtonLayout.layout)
|
|
|
|
buttonIndex += 1
|
|
rowButtonIndex += 1
|
|
}
|
|
|
|
overallMinimumRowWidth = max(overallMinimumRowWidth, maximumRowButtonWidth * CGFloat(row.buttons.count) + buttonSpacing * max(0.0, CGFloat(row.buttons.count - 1)))
|
|
finalizeRowLayouts.append(finalizeRowButtonLayouts)
|
|
|
|
rowIndex += 1
|
|
}
|
|
|
|
return (min(constrainedWidth, overallMinimumRowWidth), { constrainedWidth in
|
|
var buttonFramesAndApply: [(CGRect, () -> ChatMessageActionButtonNode)] = []
|
|
|
|
var verticalRowOffset: CGFloat = 0.0
|
|
verticalRowOffset += buttonSpacing
|
|
|
|
var rowIndex = 0
|
|
for finalizeRowButtonLayouts in finalizeRowLayouts {
|
|
let actualButtonWidth: CGFloat = max(1.0, floor((constrainedWidth - CGFloat(max(0, finalizeRowButtonLayouts.count - 1)) * buttonSpacing) / CGFloat(finalizeRowButtonLayouts.count)))
|
|
var horizontalButtonOffset: CGFloat = 0.0
|
|
for finalizeButtonLayout in finalizeRowButtonLayouts {
|
|
let (buttonSize, buttonApply) = finalizeButtonLayout(actualButtonWidth)
|
|
let buttonFrame = CGRect(origin: CGPoint(x: horizontalButtonOffset, y: verticalRowOffset), size: buttonSize)
|
|
buttonFramesAndApply.append((buttonFrame, buttonApply))
|
|
horizontalButtonOffset += buttonSize.width + buttonSpacing
|
|
}
|
|
|
|
verticalRowOffset += buttonHeight + buttonSpacing
|
|
rowIndex += 1
|
|
}
|
|
if verticalRowOffset > 0.0 {
|
|
verticalRowOffset = max(0.0, verticalRowOffset - buttonSpacing)
|
|
}
|
|
|
|
return (CGSize(width: constrainedWidth, height: verticalRowOffset), { animated in
|
|
let node: ChatMessageActionButtonsNode
|
|
if let maybeNode = maybeNode {
|
|
node = maybeNode
|
|
} else {
|
|
node = ChatMessageActionButtonsNode()
|
|
}
|
|
|
|
var updatedButtons: [ChatMessageActionButtonNode] = []
|
|
var index = 0
|
|
for (buttonFrame, buttonApply) in buttonFramesAndApply {
|
|
let buttonNode = buttonApply()
|
|
buttonNode.frame = buttonFrame
|
|
updatedButtons.append(buttonNode)
|
|
if buttonNode.supernode == nil {
|
|
node.addSubnode(buttonNode)
|
|
buttonNode.pressed = node.buttonPressedWrapper
|
|
}
|
|
index += 1
|
|
}
|
|
|
|
var buttonsUpdated = false
|
|
if node.buttonNodes.count != updatedButtons.count {
|
|
buttonsUpdated = true
|
|
} else {
|
|
for i in 0 ..< updatedButtons.count {
|
|
if updatedButtons[i] !== node.buttonNodes[i] {
|
|
buttonsUpdated = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if buttonsUpdated {
|
|
for currentButton in node.buttonNodes {
|
|
if !updatedButtons.contains(currentButton) {
|
|
currentButton.removeFromSupernode()
|
|
}
|
|
}
|
|
}
|
|
node.buttonNodes = updatedButtons
|
|
|
|
if animated {
|
|
/*UIView.transition(with: node.view, duration: 0.2, options: [.transitionCrossDissolve], animations: {
|
|
|
|
}, completion: nil)*/
|
|
}
|
|
|
|
return node
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|