mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
290 lines
14 KiB
Swift
290 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
import SyncCore
|
|
import TelegramPresentationData
|
|
import LocalizedPeerData
|
|
|
|
enum ChatMessageForwardInfoType: Equatable {
|
|
case bubble(incoming: Bool)
|
|
case standalone
|
|
}
|
|
|
|
private final class InfoButtonNode: HighlightableButtonNode {
|
|
private let pressed: () -> Void
|
|
let iconNode: ASImageNode
|
|
|
|
private var theme: ChatPresentationThemeData?
|
|
private var type: ChatMessageForwardInfoType?
|
|
|
|
init(pressed: @escaping () -> Void) {
|
|
self.pressed = pressed
|
|
|
|
self.iconNode = ASImageNode()
|
|
self.iconNode.displaysAsynchronously = false
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.iconNode)
|
|
|
|
self.addTarget(self, action: #selector(self.pressedEvent), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
@objc private func pressedEvent() {
|
|
self.pressed()
|
|
}
|
|
|
|
func update(size: CGSize, theme: ChatPresentationThemeData, type: ChatMessageForwardInfoType) {
|
|
if self.theme !== theme || self.type != type {
|
|
self.theme = theme
|
|
self.type = type
|
|
let color: UIColor
|
|
switch type {
|
|
case let .bubble(incoming):
|
|
color = incoming ? theme.theme.chat.message.incoming.accentControlColor : theme.theme.chat.message.outgoing.accentControlColor
|
|
case .standalone:
|
|
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
|
|
color = serviceColor.primaryText
|
|
}
|
|
self.iconNode.image = PresentationResourcesChat.chatPsaInfo(theme.theme, color: color.argb)
|
|
}
|
|
|
|
if let image = self.iconNode.image {
|
|
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
|
}
|
|
}
|
|
}
|
|
|
|
class ChatMessageForwardInfoNode: ASDisplayNode {
|
|
private var textNode: TextNode?
|
|
private var credibilityIconNode: ASImageNode?
|
|
private var infoNode: InfoButtonNode?
|
|
|
|
var openPsa: ((String, ASDisplayNode) -> Void)?
|
|
|
|
override init() {
|
|
super.init()
|
|
}
|
|
|
|
func hasAction(at point: CGPoint) -> Bool {
|
|
if let infoNode = self.infoNode, infoNode.frame.contains(point) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func updatePsaButtonDisplay(isVisible: Bool, animated: Bool) {
|
|
if let infoNode = self.infoNode {
|
|
if isVisible != !infoNode.iconNode.alpha.isZero {
|
|
let transition: ContainedViewLayoutTransition
|
|
if animated {
|
|
transition = .animated(duration: 0.25, curve: .easeInOut)
|
|
} else {
|
|
transition = .immediate
|
|
}
|
|
transition.updateAlpha(node: infoNode.iconNode, alpha: isVisible ? 1.0 : 0.0)
|
|
transition.updateSublayerTransformScale(node: infoNode, scale: isVisible ? 1.0 : 0.1)
|
|
}
|
|
}
|
|
}
|
|
|
|
class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) {
|
|
let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode)
|
|
|
|
return { presentationData, strings, type, peer, authorName, psaType, constrainedSize in
|
|
let fontSize = floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)
|
|
let prefixFont = Font.regular(fontSize)
|
|
let peerFont = Font.medium(fontSize)
|
|
|
|
let peerString: String
|
|
if let peer = peer {
|
|
if let authorName = authorName {
|
|
peerString = "\(peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))"
|
|
} else {
|
|
peerString = peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)
|
|
}
|
|
} else if let authorName = authorName {
|
|
peerString = authorName
|
|
} else {
|
|
peerString = ""
|
|
}
|
|
|
|
var hasPsaInfo = false
|
|
if let _ = psaType {
|
|
hasPsaInfo = true
|
|
}
|
|
|
|
let titleColor: UIColor
|
|
let completeSourceString: (String, [(Int, NSRange)])
|
|
|
|
switch type {
|
|
case let .bubble(incoming):
|
|
if let psaType = psaType {
|
|
titleColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.barPositive : presentationData.theme.theme.chat.message.outgoing.polls.barPositive
|
|
|
|
var customFormat: String?
|
|
let key = "Message.ForwardedPsa.\(psaType)"
|
|
if let string = presentationData.strings.primaryComponent.dict[key] {
|
|
customFormat = string
|
|
} else if let string = presentationData.strings.secondaryComponent?.dict[key] {
|
|
customFormat = string
|
|
}
|
|
|
|
if let customFormat = customFormat {
|
|
if let range = customFormat.range(of: "%@") {
|
|
let leftPart = String(customFormat[customFormat.startIndex ..< range.lowerBound])
|
|
let rightPart = String(customFormat[range.upperBound...])
|
|
|
|
let formattedText = leftPart + peerString + rightPart
|
|
completeSourceString = (formattedText, [(0, NSRange(location: leftPart.count, length: peerString.count))])
|
|
} else {
|
|
completeSourceString = (customFormat, [])
|
|
}
|
|
} else {
|
|
completeSourceString = strings.Message_GenericForwardedPsa(peerString)
|
|
}
|
|
} else {
|
|
titleColor = incoming ? presentationData.theme.theme.chat.message.incoming.accentTextColor : presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
|
completeSourceString = strings.Message_ForwardedMessage(peerString)
|
|
}
|
|
case .standalone:
|
|
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
|
titleColor = serviceColor.primaryText
|
|
|
|
if let psaType = psaType {
|
|
var customFormat: String?
|
|
let key = "Message.ForwardedPsa.\(psaType)"
|
|
if let string = presentationData.strings.primaryComponent.dict[key] {
|
|
customFormat = string
|
|
} else if let string = presentationData.strings.secondaryComponent?.dict[key] {
|
|
customFormat = string
|
|
}
|
|
|
|
if let customFormat = customFormat {
|
|
if let range = customFormat.range(of: "%@") {
|
|
let leftPart = String(customFormat[customFormat.startIndex ..< range.lowerBound])
|
|
let rightPart = String(customFormat[range.upperBound...])
|
|
|
|
let formattedText = leftPart + peerString + rightPart
|
|
completeSourceString = (formattedText, [(0, NSRange(location: leftPart.count, length: peerString.count))])
|
|
} else {
|
|
completeSourceString = (customFormat, [])
|
|
}
|
|
} else {
|
|
completeSourceString = strings.Message_GenericForwardedPsa(peerString)
|
|
}
|
|
} else {
|
|
completeSourceString = strings.Message_ForwardedMessageShort(peerString)
|
|
}
|
|
}
|
|
|
|
var currentCredibilityIconImage: UIImage?
|
|
var highlight = true
|
|
if let peer = peer {
|
|
if let channel = peer as? TelegramChannel, channel.username == nil {
|
|
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
|
|
} else if case .member = channel.participationStatus {
|
|
} else {
|
|
highlight = false
|
|
}
|
|
}
|
|
|
|
if peer.isScam {
|
|
switch type {
|
|
case let .bubble(incoming):
|
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
|
case .standalone:
|
|
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: .service)
|
|
}
|
|
} else {
|
|
currentCredibilityIconImage = nil
|
|
}
|
|
} else {
|
|
highlight = false
|
|
}
|
|
|
|
let completeString: NSString = completeSourceString.0 as NSString
|
|
let string = NSMutableAttributedString(string: completeString as String, attributes: [NSAttributedString.Key.foregroundColor: titleColor, NSAttributedString.Key.font: prefixFont])
|
|
if highlight, let range = completeSourceString.1.first?.1 {
|
|
string.addAttributes([NSAttributedString.Key.font: peerFont], range: range)
|
|
}
|
|
|
|
var credibilityIconWidth: CGFloat = 0.0
|
|
if let icon = currentCredibilityIconImage {
|
|
credibilityIconWidth += icon.size.width + 4.0
|
|
}
|
|
|
|
var infoWidth: CGFloat = 0.0
|
|
if hasPsaInfo {
|
|
infoWidth += 32.0
|
|
}
|
|
|
|
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
|
|
|
return (CGSize(width: textLayout.size.width + credibilityIconWidth + infoWidth, height: textLayout.size.height), { width in
|
|
let node: ChatMessageForwardInfoNode
|
|
if let maybeNode = maybeNode {
|
|
node = maybeNode
|
|
} else {
|
|
node = ChatMessageForwardInfoNode()
|
|
}
|
|
|
|
let textNode = textApply()
|
|
if node.textNode == nil {
|
|
textNode.isUserInteractionEnabled = false
|
|
node.textNode = textNode
|
|
node.addSubnode(textNode)
|
|
}
|
|
textNode.frame = CGRect(origin: CGPoint(), size: textLayout.size)
|
|
|
|
if let credibilityIconImage = currentCredibilityIconImage {
|
|
let credibilityIconNode: ASImageNode
|
|
if let node = node.credibilityIconNode {
|
|
credibilityIconNode = node
|
|
} else {
|
|
credibilityIconNode = ASImageNode()
|
|
node.credibilityIconNode = credibilityIconNode
|
|
node.addSubnode(credibilityIconNode)
|
|
}
|
|
credibilityIconNode.frame = CGRect(origin: CGPoint(x: textLayout.size.width + 4.0, y: 16.0), size: credibilityIconImage.size)
|
|
credibilityIconNode.image = credibilityIconImage
|
|
} else {
|
|
node.credibilityIconNode?.removeFromSupernode()
|
|
node.credibilityIconNode = nil
|
|
}
|
|
|
|
if hasPsaInfo {
|
|
let infoNode: InfoButtonNode
|
|
if let current = node.infoNode {
|
|
infoNode = current
|
|
} else {
|
|
infoNode = InfoButtonNode(pressed: { [weak node] in
|
|
guard let node = node else {
|
|
return
|
|
}
|
|
if let psaType = psaType, let infoNode = node.infoNode {
|
|
node.openPsa?(psaType, infoNode)
|
|
}
|
|
})
|
|
node.infoNode = infoNode
|
|
node.addSubnode(infoNode)
|
|
}
|
|
let infoButtonSize = CGSize(width: 32.0, height: 32.0)
|
|
let infoButtonFrame = CGRect(origin: CGPoint(x: width - infoButtonSize.width - 2.0, y: 1.0), size: infoButtonSize)
|
|
infoNode.frame = infoButtonFrame
|
|
infoNode.update(size: infoButtonFrame.size, theme: presentationData.theme, type: type)
|
|
} else if let infoNode = node.infoNode {
|
|
node.infoNode = nil
|
|
infoNode.removeFromSupernode()
|
|
}
|
|
|
|
return node
|
|
})
|
|
}
|
|
}
|
|
}
|