mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Various improvements
This commit is contained in:
@@ -42,8 +42,11 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
private var titleBadgeButton: HighlightTrackingButtonNode?
|
||||
private let textClippingNode: ASDisplayNode
|
||||
private let textNode: TextNode
|
||||
private let additionalTextNode: TextNode
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
private let lineNode: ASDisplayNode
|
||||
|
||||
private var maskView: UIImageView?
|
||||
private var maskOverlayView: UIView?
|
||||
|
||||
@@ -55,13 +58,17 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
private var isExpanded: Bool = false
|
||||
private var appliedIsExpanded: Bool = false
|
||||
|
||||
private var countryName: String?
|
||||
|
||||
required public init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleBadgeLabel = TextNode()
|
||||
self.textClippingNode = ASDisplayNode()
|
||||
self.textNode = TextNode()
|
||||
self.additionalTextNode = TextNode()
|
||||
self.expandIcon = ASImageNode()
|
||||
self.statusNode = ChatMessageDateAndStatusNode()
|
||||
self.lineNode = ASDisplayNode()
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -80,6 +87,14 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textClippingNode.addSubnode(self.textNode)
|
||||
|
||||
self.additionalTextNode.isUserInteractionEnabled = false
|
||||
self.additionalTextNode.contentMode = .topLeft
|
||||
self.additionalTextNode.contentsScale = UIScreenScale
|
||||
self.additionalTextNode.displaysAsynchronously = false
|
||||
self.textClippingNode.addSubnode(self.additionalTextNode)
|
||||
|
||||
self.textClippingNode.addSubnode(self.lineNode)
|
||||
|
||||
self.titleBadgeLabel.isUserInteractionEnabled = false
|
||||
self.titleBadgeLabel.contentMode = .topLeft
|
||||
self.titleBadgeLabel.contentsScale = UIScreenScale
|
||||
@@ -106,24 +121,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
}
|
||||
|
||||
@objc private func badgePressed() {
|
||||
guard let item = self.item else {
|
||||
guard let item = self.item, let countryName = self.countryName else {
|
||||
return
|
||||
}
|
||||
|
||||
var countryId: String?
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? FactCheckMessageAttribute, case let .Loaded(_, _, countryIdValue) = attribute.content {
|
||||
countryId = countryIdValue
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
guard let countryId else {
|
||||
return
|
||||
}
|
||||
|
||||
let locale = localeWithStrings(item.presentationData.strings)
|
||||
let countryName = displayCountryName(countryId, locale: locale)
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_FactCheck_Description(countryName).string, true, self.titleBadgeButton, nil)
|
||||
}
|
||||
|
||||
@@ -148,13 +149,9 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
}
|
||||
|
||||
let textNodeFrame = self.textClippingNode.frame
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: false)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
@@ -216,10 +213,12 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let titleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel)
|
||||
let textLayout = TextNode.asyncLayout(self.textNode)
|
||||
let additionalTextLayout = TextNode.asyncLayout(self.additionalTextNode)
|
||||
let measureTextLayout = TextNode.asyncLayout(nil)
|
||||
let statusLayout = self.statusNode.asyncLayout()
|
||||
|
||||
let currentIsExpanded = self.isExpanded
|
||||
let currentCountryName = self.countryName
|
||||
|
||||
return { item, layoutConstants, _, _, _, _ in
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
@@ -313,15 +312,32 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let titleBadgeString = NSAttributedString(string: item.presentationData.strings.Message_FactCheck_WhatIsThis, font: badgeFont, textColor: mainColor)
|
||||
let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayout(TextNodeLayoutArguments(attributedString: titleBadgeString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize))
|
||||
|
||||
let countryName: String
|
||||
if let currentCountryName {
|
||||
countryName = currentCountryName
|
||||
} else {
|
||||
if let attribute = item.message.factCheckAttribute, case let .Loaded(_, _, countryIdValue) = attribute.content {
|
||||
let locale = localeWithStrings(item.presentationData.strings)
|
||||
countryName = displayCountryName(countryIdValue, locale: locale)
|
||||
} else {
|
||||
countryName = ""
|
||||
}
|
||||
}
|
||||
|
||||
let finalAttributedText = stringWithAppliedEntities(rawText, entities: rawEntities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil) as! NSMutableAttributedString
|
||||
finalAttributedText.append(NSAttributedString(string: "__", font: textFont, textColor: .clear))
|
||||
|
||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: finalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
||||
|
||||
let additionalAttributedText = NSMutableAttributedString(string: item.presentationData.strings.Conversation_FactCheck_InnerDescription(countryName).string, font: badgeFont, textColor: mainColor)
|
||||
additionalAttributedText.append(NSAttributedString(string: "__", font: badgeFont, textColor: .clear))
|
||||
|
||||
let (additionalTextLayout, additionalTextApply) = additionalTextLayout(TextNodeLayoutArguments(attributedString: additionalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
||||
|
||||
var canExpand = false
|
||||
var clippedTextHeight: CGFloat = textLayout.size.height
|
||||
if textLayout.numberOfLines > 4 {
|
||||
let (measuredTextLayout, _) = measureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
||||
let (measuredTextLayout, _) = measureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
||||
canExpand = true
|
||||
|
||||
if !currentIsExpanded {
|
||||
@@ -340,7 +356,11 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let textFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: -textInsets.top + titleFrameWithoutInsets.height + textSpacing), size: textLayout.size)
|
||||
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: clippedTextHeight - textInsets.top - textInsets.bottom))
|
||||
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||
|
||||
|
||||
let additionalTextFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: textFrame.maxY), size: additionalTextLayout.size)
|
||||
var additionalTextFrameWithoutInsets = CGRect(origin: CGPoint(x: additionalTextFrame.origin.x + textInsets.left, y: additionalTextFrame.origin.y + textInsets.top), size: CGSize(width: additionalTextFrame.width - textInsets.left - textInsets.right, height: additionalTextFrame.height - textInsets.top - textInsets.bottom))
|
||||
additionalTextFrameWithoutInsets = additionalTextFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||
|
||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||
if let statusType = statusType {
|
||||
var isReplyThread = false
|
||||
@@ -377,6 +397,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
|
||||
}
|
||||
suggestedBoundingWidth = max(suggestedBoundingWidth, additionalTextFrameWithoutInsets.width)
|
||||
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||
suggestedBoundingWidth += (sideInsets - 2.0) * 2.0
|
||||
|
||||
@@ -385,7 +406,13 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right)
|
||||
|
||||
boundingSize = CGSize(width: boundingWidth, height: topInset + titleFrameWithoutInsets.height + textFrameWithoutInsets.size.height + textSpacing)
|
||||
var contentHeight = titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.size.height
|
||||
if canExpand && !currentIsExpanded {
|
||||
} else {
|
||||
contentHeight += textSpacing * 2.0 + 1.0 + additionalTextFrameWithoutInsets.height
|
||||
}
|
||||
contentHeight += textSpacing
|
||||
boundingSize = CGSize(width: boundingWidth, height: topInset + contentHeight - textSpacing)
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
boundingSize.height += statusSizeAndApply.0.height
|
||||
}
|
||||
@@ -400,7 +427,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme
|
||||
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.countryName = countryName
|
||||
|
||||
let backgroundView: MessageInlineBlockBackgroundView
|
||||
if let current = strongSelf.backgroundView {
|
||||
backgroundView = current
|
||||
@@ -410,6 +438,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
strongSelf.backgroundView = backgroundView
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
strongSelf.lineNode.backgroundColor = mainColor.withAlphaComponent(0.15)
|
||||
}
|
||||
|
||||
var isExpandedUpdated = false
|
||||
if strongSelf.appliedIsExpanded != currentIsExpanded {
|
||||
strongSelf.appliedIsExpanded = currentIsExpanded
|
||||
@@ -464,57 +496,12 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.textNode.frame = CGRect(origin: .zero, size: textFrame.size)
|
||||
|
||||
var clippingTextFrame = textFrame.offsetBy(dx: 0.0, dy: topInset)
|
||||
clippingTextFrame.size.height = clippedTextHeight - 3.0
|
||||
|
||||
if canExpand {
|
||||
let wasHidden = strongSelf.expandIcon.isHidden
|
||||
strongSelf.expandIcon.isHidden = false
|
||||
if strongSelf.maskView?.image == nil {
|
||||
strongSelf.maskView?.image = generateMaskImage()
|
||||
}
|
||||
strongSelf.textClippingNode.view.mask = strongSelf.maskView
|
||||
|
||||
var expandIconFrame: CGRect = .zero
|
||||
if let icon = strongSelf.expandIcon.image {
|
||||
expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: clippingTextFrame.maxY - icon.size.height - 5.0), size: icon.size)
|
||||
if wasHidden || isFirstTime {
|
||||
strongSelf.expandIcon.position = expandIconFrame.center
|
||||
} else {
|
||||
animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil)
|
||||
}
|
||||
strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size)
|
||||
}
|
||||
|
||||
let expandButtonFrame = expandIconFrame.insetBy(dx: -8.0, dy: -8.0)
|
||||
|
||||
let expandButton: HighlightTrackingButtonNode
|
||||
if let current = strongSelf.expandButton {
|
||||
expandButton = current
|
||||
} else {
|
||||
expandButton = HighlightTrackingButtonNode()
|
||||
expandButton.addTarget(self, action: #selector(strongSelf.expandPressed), forControlEvents: .touchUpInside)
|
||||
expandButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.expandIcon.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.expandIcon.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.expandIcon.alpha = 1.0
|
||||
strongSelf.expandIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.expandButton = expandButton
|
||||
strongSelf.addSubnode(expandButton)
|
||||
}
|
||||
expandButton.frame = expandButtonFrame
|
||||
} else {
|
||||
strongSelf.expandIcon.isHidden = true
|
||||
strongSelf.textClippingNode.view.mask = nil
|
||||
}
|
||||
let _ = additionalTextApply()
|
||||
strongSelf.additionalTextNode.frame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textInsets.bottom + textSpacing + 1.0), size: additionalTextFrame.size)
|
||||
|
||||
let clippingTextFrame = CGRect(origin: textFrame.origin.offsetBy(dx: 0.0, dy: topInset), size: CGSize(width: boundingWidth, height: contentHeight - titleFrame.height + textSpacing))
|
||||
|
||||
var titleLineWidth: CGFloat = 0.0
|
||||
if let firstLine = titleLayout.linesRects().first {
|
||||
titleLineWidth = firstLine.width
|
||||
@@ -559,7 +546,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
titleBadgeButton.setBackgroundImage(generateFilledCircleImage(diameter: badgeBackgroundFrame.height, color: mainColor.withMultipliedAlpha(0.1))?.stretchableImage(withLeftCapWidth: Int(badgeBackgroundFrame.height / 2), topCapHeight: Int(badgeBackgroundFrame.height / 2)), for: .normal)
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + topInset), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.height + textSpacing))
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + topInset), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: contentHeight))
|
||||
|
||||
if isFirstTime {
|
||||
strongSelf.textClippingNode.frame = clippingTextFrame
|
||||
@@ -578,6 +565,55 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
}
|
||||
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: isFirstTime ? .None : animation)
|
||||
|
||||
animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textSpacing + 1.0), size: CGSize(width: backgroundFrame.width - 9.0 - 6.0, height: 1.0 - UIScreenPixel)), completion: nil)
|
||||
|
||||
if canExpand {
|
||||
let wasHidden = strongSelf.expandIcon.isHidden
|
||||
strongSelf.expandIcon.isHidden = false
|
||||
if strongSelf.maskView?.image == nil {
|
||||
strongSelf.maskView?.image = generateMaskImage()
|
||||
}
|
||||
strongSelf.textClippingNode.view.mask = strongSelf.maskView
|
||||
|
||||
var expandIconFrame: CGRect = .zero
|
||||
if let icon = strongSelf.expandIcon.image {
|
||||
expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: backgroundFrame.maxY - icon.size.height - 6.0), size: icon.size)
|
||||
if wasHidden || isFirstTime {
|
||||
strongSelf.expandIcon.position = expandIconFrame.center
|
||||
} else {
|
||||
animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil)
|
||||
}
|
||||
strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size)
|
||||
}
|
||||
|
||||
let expandButtonFrame = expandIconFrame.insetBy(dx: -8.0, dy: -8.0)
|
||||
|
||||
let expandButton: HighlightTrackingButtonNode
|
||||
if let current = strongSelf.expandButton {
|
||||
expandButton = current
|
||||
} else {
|
||||
expandButton = HighlightTrackingButtonNode()
|
||||
expandButton.addTarget(self, action: #selector(strongSelf.expandPressed), forControlEvents: .touchUpInside)
|
||||
expandButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.expandIcon.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.expandIcon.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.expandIcon.alpha = 1.0
|
||||
strongSelf.expandIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.expandButton = expandButton
|
||||
strongSelf.addSubnode(expandButton)
|
||||
}
|
||||
expandButton.frame = expandButtonFrame
|
||||
} else {
|
||||
strongSelf.expandIcon.isHidden = true
|
||||
strongSelf.textClippingNode.view.mask = nil
|
||||
}
|
||||
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
strongSelf.statusNode.reactionSelected = { [weak strongSelf] _, value, sourceView in
|
||||
guard let strongSelf, let item = strongSelf.item else {
|
||||
@@ -594,7 +630,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
|
||||
}
|
||||
|
||||
let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: topInset + textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
||||
let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: backgroundFrame.maxY + 4.0), size: statusSizeAndApply.0)
|
||||
if isFirstTime {
|
||||
strongSelf.statusNode.frame = statusFrame
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user