Merge commit '21898f4207e36bc78481d88ba681d87b2380a1ad'

This commit is contained in:
Isaac 2024-05-20 18:51:42 +04:00
commit b420532822
9 changed files with 343 additions and 45 deletions

View File

@ -323,6 +323,7 @@ public final class ListComposePollOptionComponent: Component {
externalState: component.externalState ?? TextFieldComponent.ExternalState(), externalState: component.externalState ?? TextFieldComponent.ExternalState(),
fontSize: 17.0, fontSize: 17.0,
textColor: component.theme.list.itemPrimaryTextColor, textColor: component.theme.list.itemPrimaryTextColor,
accentColor: component.theme.list.itemPrimaryTextColor,
insets: UIEdgeInsets(top: verticalInset, left: 8.0, bottom: verticalInset, right: 8.0), insets: UIEdgeInsets(top: verticalInset, left: 8.0, bottom: verticalInset, right: 8.0),
hideKeyboard: component.inputMode == .emoji, hideKeyboard: component.inputMode == .emoji,
customInputView: nil, customInputView: nil,

View File

@ -84,7 +84,7 @@ final class TooltipControllerNode: ASDisplayNode {
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout self.validLayout = layout
let maxWidth = layout.size.width - 20.0 let maxWidth = layout.size.width - 20.0 - self.padding * 2.0
let contentSize: CGSize let contentSize: CGSize

View File

@ -16,7 +16,10 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramCore", "//submodules/TelegramCore",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/TelegramStringFormatting",
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/Geocoding",
"//submodules/UrlEscaping",
"//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",

View File

@ -6,33 +6,67 @@ import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting
import TextFormat import TextFormat
import ChatMessageDateAndStatusNode import ChatMessageDateAndStatusNode
import ChatMessageBubbleContentNode import ChatMessageBubbleContentNode
import ChatMessageItemCommon import ChatMessageItemCommon
import MessageInlineBlockBackgroundView import MessageInlineBlockBackgroundView
import Geocoding
import UrlEscaping
private func generateMaskImage() -> UIImage? {
return generateImage(CGSize(width: 140, height: 30), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: .zero, size: size))
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.setBlendMode(.copy)
context.clip(to: CGRect(origin: CGPoint(x: 10.0, y: 8.0), size: CGSize(width: 130.0, height: 22.0)))
context.drawLinearGradient(gradient, start: CGPoint(x: 10.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 22.0, right: 130.0))
}
public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode { public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode {
private let backgroundView: MessageInlineBlockBackgroundView private var backgroundView: MessageInlineBlockBackgroundView?
private var titleNode: TextNode private var titleNode: TextNode
private var titleBadgeLabel: TextNode private var titleBadgeLabel: TextNode
private var titleBadgeButton: HighlightTrackingButtonNode? private var titleBadgeButton: HighlightTrackingButtonNode?
private let textClippingNode: ASDisplayNode
private let textNode: TextNode private let textNode: TextNode
private var linkHighlightingNode: LinkHighlightingNode?
private var maskView: UIImageView?
private var maskOverlayView: UIView?
private var expandIcon: ASImageNode
private var expandButton: HighlightTrackingButtonNode?
private let statusNode: ChatMessageDateAndStatusNode private let statusNode: ChatMessageDateAndStatusNode
required public init() { private var isExpanded: Bool = false
self.backgroundView = MessageInlineBlockBackgroundView() private var appliedIsExpanded: Bool = false
required public init() {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleBadgeLabel = TextNode() self.titleBadgeLabel = TextNode()
self.textClippingNode = ASDisplayNode()
self.textNode = TextNode() self.textNode = TextNode()
self.expandIcon = ASImageNode()
self.statusNode = ChatMessageDateAndStatusNode() self.statusNode = ChatMessageDateAndStatusNode()
super.init() super.init()
self.view.addSubview(self.backgroundView) self.textClippingNode.clipsToBounds = true
self.addSubnode(self.textClippingNode)
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .topLeft self.titleNode.contentMode = .topLeft
@ -44,29 +78,145 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
self.textNode.contentMode = .topLeft self.textNode.contentMode = .topLeft
self.textNode.contentsScale = UIScreenScale self.textNode.contentsScale = UIScreenScale
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.addSubnode(self.textNode) self.textClippingNode.addSubnode(self.textNode)
self.titleBadgeLabel.isUserInteractionEnabled = false self.titleBadgeLabel.isUserInteractionEnabled = false
self.titleBadgeLabel.contentMode = .topLeft self.titleBadgeLabel.contentMode = .topLeft
self.titleBadgeLabel.contentsScale = UIScreenScale self.titleBadgeLabel.contentsScale = UIScreenScale
self.titleBadgeLabel.displaysAsynchronously = false self.titleBadgeLabel.displaysAsynchronously = false
self.addSubnode(self.titleBadgeLabel) self.addSubnode(self.titleBadgeLabel)
self.expandIcon.displaysAsynchronously = false
self.addSubnode(self.expandIcon)
} }
required public init?(coder aDecoder: NSCoder) { required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc private func badgePressed() { public override func didLoad() {
self.maskView = UIImageView()
let maskOverlayView = UIView()
maskOverlayView.alpha = 0.0
maskOverlayView.backgroundColor = .white
self.maskOverlayView = maskOverlayView
self.maskView?.addSubview(maskOverlayView)
}
@objc private func badgePressed() {
guard let item = self.item 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, self.titleBadgeButton, nil)
}
@objc private func expandPressed() {
self.isExpanded = !self.isExpanded
guard let item = self.item else{
return
}
let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
public override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if let expandButton = self.expandButton, expandButton.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
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 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)))
} 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 {
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
}
}
if let titleBadgeButton = self.titleBadgeButton, titleBadgeButton.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
return ChatMessageBubbleContentTapAction(content: .none)
}
public override func updateTouchesAtPoint(_ point: CGPoint?) {
guard let item = self.item else {
return
}
var rects: [CGRect]?
if let point = point {
let textNodeFrame = self.textClippingNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag,
TelegramTextAttributes.BankCard
]
for name in possibleNames {
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
rects = self.textNode.attributeRects(name: name, at: index)
break
}
}
}
}
if let rects {
let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textClippingNode)
}
linkHighlightingNode.frame = self.textClippingNode.frame
linkHighlightingNode.updateRects(rects)
} else if let linkHighlightingNode = self.linkHighlightingNode {
self.linkHighlightingNode = nil
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
linkHighlightingNode?.removeFromSupernode()
})
}
} }
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let titleLayout = TextNode.asyncLayout(self.titleNode) let titleLayout = TextNode.asyncLayout(self.titleNode)
let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel) let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel)
let textLayout = TextNode.asyncLayout(self.textNode) let textLayout = TextNode.asyncLayout(self.textNode)
let measureTextLayout = TextNode.asyncLayout(nil)
let statusLayout = self.statusNode.asyncLayout() let statusLayout = self.statusNode.asyncLayout()
let currentIsExpanded = self.isExpanded
return { item, layoutConstants, _, _, _, _ in return { item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
@ -78,7 +228,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let maxTextWidth = CGFloat.greatestFiniteMagnitude let maxTextWidth = CGFloat.greatestFiniteMagnitude
let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset * 2.0), height: constrainedSize.height) let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - (horizontalInset - 2.0) * 2.0), height: constrainedSize.height)
var edited = false var edited = false
if item.attributes.updatingMedia != nil { if item.attributes.updatingMedia != nil {
@ -157,7 +307,31 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let titleBadgeString = NSAttributedString(string: item.presentationData.strings.Message_FactCheck_WhatIsThis, font: badgeFont, textColor: mainColor) 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 (titleBadgeLayout, titleBadgeApply) = titleBadgeLayout(TextNodeLayoutArguments(attributedString: titleBadgeString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize))
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
// var collapsedNumberOfLines = 3
// if measuredTextLayout.numberOfLines == 4 {
// collapsedNumberOfLines = 4
// }
// let canExpand = collapsedNumberOfLines < measuredTextLayout.numberOfLines
var finalAttributedText = attributedText
if "".isEmpty {
finalAttributedText = stringWithAppliedEntities(rawText + "\u{00A0}\u{00A0}\u{00A0}", entities: rawEntities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil)
}
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: finalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor, customTruncationToken: NSAttributedString(string: "", font: textFont, textColor: .clear)))
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))
canExpand = true
if !currentIsExpanded {
clippedTextHeight = measuredTextLayout.size.height
}
}
var titleFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: titleLayout.size) var titleFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: titleLayout.size)
titleFrame = titleFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left * 2.0 - 2.0, dy: layoutConstants.text.bubbleInsets.top - 3.0) titleFrame = titleFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left * 2.0 - 2.0, dy: layoutConstants.text.bubbleInsets.top - 3.0)
@ -167,7 +341,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let textSpacing: CGFloat = 3.0 let textSpacing: CGFloat = 3.0
let textFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: -textInsets.top + titleFrameWithoutInsets.height + textSpacing), size: textLayout.size) 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: textFrame.height - textInsets.top - textInsets.bottom)) 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) textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
@ -207,29 +381,52 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0) suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
} }
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
suggestedBoundingWidth += sideInsets suggestedBoundingWidth += (sideInsets - 2.0) * 2.0
return (suggestedBoundingWidth, { boundingWidth in return (suggestedBoundingWidth, { boundingWidth in
var boundingSize: CGSize var boundingSize: CGSize
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth) let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth)
boundingSize = CGSize(width: textFrameWithoutInsets.size.width, height: titleFrameWithoutInsets.height + textFrameWithoutInsets.size.height + textSpacing) boundingSize = CGSize(width: boundingWidth, height: titleFrameWithoutInsets.height + textFrameWithoutInsets.size.height + textSpacing)
if let statusSizeAndApply = statusSizeAndApply { if let statusSizeAndApply = statusSizeAndApply {
boundingSize.height += statusSizeAndApply.0.height boundingSize.height += statusSizeAndApply.0.height
} }
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
return (boundingSize, { [weak self] animation, _, _ in return (boundingSize, { [weak self] animation, _, info in
if let strongSelf = self { if let strongSelf = self {
info?.setInvertOffsetDirection()
let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme
strongSelf.item = item strongSelf.item = item
let backgroundView: MessageInlineBlockBackgroundView
if let current = strongSelf.backgroundView {
backgroundView = current
} else {
backgroundView = MessageInlineBlockBackgroundView()
strongSelf.view.insertSubview(backgroundView, at: 0)
strongSelf.backgroundView = backgroundView
}
var isExpandedUpdated = false
if strongSelf.appliedIsExpanded != currentIsExpanded {
strongSelf.appliedIsExpanded = currentIsExpanded
info?.setInvertOffsetDirection()
isExpandedUpdated = true
animation.transition.updateTransformRotation(node: strongSelf.expandIcon, angle: currentIsExpanded ? .pi : 0.0)
if let maskOverlayView = strongSelf.maskOverlayView {
animation.transition.updateAlpha(layer: maskOverlayView.layer, alpha: currentIsExpanded ? 1.0 : 0.0)
}
}
let cachedLayout = strongSelf.textNode.cachedLayout let cachedLayout = strongSelf.textNode.cachedLayout
if case .System = animation { if case .System = animation, !isExpandedUpdated {
if let cachedLayout = cachedLayout { if let cachedLayout = cachedLayout {
if !cachedLayout.areLinesEqual(to: textLayout) { if !cachedLayout.areLinesEqual(to: textLayout) {
if let textContents = strongSelf.textNode.contents { if let textContents = strongSelf.textNode.contents {
@ -238,7 +435,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
fadeNode.contents = textContents fadeNode.contents = textContents
fadeNode.frame = strongSelf.textNode.frame fadeNode.frame = strongSelf.textNode.frame
fadeNode.isLayerBacked = true fadeNode.isLayerBacked = true
strongSelf.addSubnode(fadeNode) strongSelf.textClippingNode.addSubnode(fadeNode)
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
fadeNode?.removeFromSupernode() fadeNode?.removeFromSupernode()
}) })
@ -248,12 +445,77 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
} }
} }
if themeUpdated {
strongSelf.expandIcon.image = generateImage(CGSize(width: 15.0, height: 9.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(mainColor.cgColor)
context.setLineWidth(2.0 - UIScreenPixel)
context.setLineCap(.round)
context.setLineJoin(.round)
context.beginPath()
context.move(to: CGPoint(x: 1.0 + UIScreenPixel, y: 1.0))
context.addLine(to: CGPoint(x: size.width / 2.0, y: size.height - 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0 - UIScreenPixel, y: 1.0))
context.strokePath()
})
}
let _ = titleApply() let _ = titleApply()
let _ = textApply() strongSelf.titleNode.frame = titleFrame
let _ = titleBadgeApply() let _ = titleBadgeApply()
strongSelf.titleNode.frame = titleFrame let _ = textApply()
strongSelf.textNode.frame = textFrame strongSelf.textNode.frame = CGRect(origin: .zero, size: textFrame.size)
var clippingTextFrame = textFrame
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 {
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
}
var titleLineWidth: CGFloat = 0.0 var titleLineWidth: CGFloat = 0.0
if let firstLine = titleLayout.linesRects().first { if let firstLine = titleLayout.linesRects().first {
@ -269,45 +531,54 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.titleBadgeLabel.frame = titleBadgeFrame strongSelf.titleBadgeLabel.frame = titleBadgeFrame
let button: HighlightTrackingButtonNode let titleBadgeButton: HighlightTrackingButtonNode
if let current = strongSelf.titleBadgeButton { if let current = strongSelf.titleBadgeButton {
button = current titleBadgeButton = current
button.bounds = CGRect(origin: .zero, size: badgeBackgroundFrame.size) titleBadgeButton.bounds = CGRect(origin: .zero, size: badgeBackgroundFrame.size)
animation.animator.updatePosition(layer: button.layer, position: badgeBackgroundFrame.center, completion: nil) animation.animator.updatePosition(layer: titleBadgeButton.layer, position: badgeBackgroundFrame.center, completion: nil)
} else { } else {
button = HighlightTrackingButtonNode() titleBadgeButton = HighlightTrackingButtonNode()
button.addTarget(self, action: #selector(strongSelf.badgePressed), forControlEvents: .touchUpInside) titleBadgeButton.addTarget(self, action: #selector(strongSelf.badgePressed), forControlEvents: .touchUpInside)
button.frame = badgeBackgroundFrame titleBadgeButton.frame = badgeBackgroundFrame
button.highligthedChanged = { [weak self, weak button] highlighted in titleBadgeButton.highligthedChanged = { [weak self, weak titleBadgeButton] highlighted in
if let strongSelf = self, let button { if let strongSelf = self, let titleBadgeButton {
if highlighted { if highlighted {
button.layer.removeAnimation(forKey: "opacity") titleBadgeButton.layer.removeAnimation(forKey: "opacity")
button.alpha = 0.4 titleBadgeButton.alpha = 0.4
strongSelf.titleBadgeLabel.layer.removeAnimation(forKey: "opacity") strongSelf.titleBadgeLabel.layer.removeAnimation(forKey: "opacity")
strongSelf.titleBadgeLabel.alpha = 0.4 strongSelf.titleBadgeLabel.alpha = 0.4
} else { } else {
button.alpha = 1.0 titleBadgeButton.alpha = 1.0
button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) titleBadgeButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.titleBadgeLabel.alpha = 1.0 strongSelf.titleBadgeLabel.alpha = 1.0
strongSelf.titleBadgeLabel.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) strongSelf.titleBadgeLabel.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
} }
} }
} }
strongSelf.titleBadgeButton = button strongSelf.titleBadgeButton = titleBadgeButton
strongSelf.addSubnode(button) strongSelf.addSubnode(titleBadgeButton)
} }
if themeUpdated || button.backgroundImage(for: .normal) == nil { if themeUpdated || titleBadgeButton.backgroundImage(for: .normal) == nil {
button.setBackgroundImage(generateFilledCircleImage(diameter: badgeBackgroundFrame.height, color: mainColor.withMultipliedAlpha(0.1))?.stretchableImage(withLeftCapWidth: Int(badgeBackgroundFrame.height / 2), topCapHeight: Int(badgeBackgroundFrame.height / 2)), for: .normal) 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), 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), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.height + textSpacing))
strongSelf.backgroundView.frame = backgroundFrame animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: clippingTextFrame, completion: nil)
strongSelf.backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: .None) if let maskView = strongSelf.maskView, let maskOverlayView = strongSelf.maskOverlayView {
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil)
animation.animator.updateFrame(layer: maskOverlayView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil)
}
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: animation)
if let statusSizeAndApply = statusSizeAndApply { if let statusSizeAndApply = statusSizeAndApply {
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0) let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
animation.animator.updatePosition(layer: strongSelf.statusNode.layer, position: statusFrame.center, completion: nil)
strongSelf.statusNode.bounds = CGRect(origin: .zero, size: statusFrame.size)
if strongSelf.statusNode.supernode == nil { if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode) strongSelf.addSubnode(strongSelf.statusNode)
statusSizeAndApply.1(.None) statusSizeAndApply.1(.None)

View File

@ -211,6 +211,7 @@ private final class FactCheckAlertContentNode: AlertContentNode {
externalState: self.inputFieldExternalState, externalState: self.inputFieldExternalState,
fontSize: 14.0, fontSize: 14.0,
textColor: self.presentationTheme.actionSheet.inputTextColor, textColor: self.presentationTheme.actionSheet.inputTextColor,
accentColor: self.presentationTheme.actionSheet.controlAccentColor,
insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0), insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0),
hideKeyboard: false, hideKeyboard: false,
customInputView: nil, customInputView: nil,
@ -218,7 +219,7 @@ private final class FactCheckAlertContentNode: AlertContentNode {
isOneLineWhenUnfocused: false, isOneLineWhenUnfocused: false,
characterLimit: nil, characterLimit: nil,
emptyLineHandling: .oneConsecutive, emptyLineHandling: .oneConsecutive,
formatMenuAvailability: .available([.bold, .italic, .monospace, .link, .strikethrough, .underline]), formatMenuAvailability: .available([.bold, .italic, .link]),
returnKeyType: .done, returnKeyType: .done,
lockedFormatAction: { lockedFormatAction: {
}, },

View File

@ -257,6 +257,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
externalState: self.textFieldExternalState, externalState: self.textFieldExternalState,
fontSize: 17.0, fontSize: 17.0,
textColor: component.theme.list.itemPrimaryTextColor, textColor: component.theme.list.itemPrimaryTextColor,
accentColor: component.theme.list.itemPrimaryTextColor,
insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0 + measureTextLimitInset), insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0 + measureTextLimitInset),
hideKeyboard: false, hideKeyboard: false,
customInputView: nil, customInputView: nil,

View File

@ -781,6 +781,7 @@ public final class MessageInputPanelComponent: Component {
externalState: self.textFieldExternalState, externalState: self.textFieldExternalState,
fontSize: 17.0, fontSize: 17.0,
textColor: UIColor(rgb: 0xffffff), textColor: UIColor(rgb: 0xffffff),
accentColor: UIColor(rgb: 0xffffff),
insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0), insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0),
hideKeyboard: component.hideKeyboard, hideKeyboard: component.hideKeyboard,
customInputView: component.customInputView, customInputView: component.customInputView,

View File

@ -124,6 +124,7 @@ public final class TextFieldComponent: Component {
public let externalState: ExternalState public let externalState: ExternalState
public let fontSize: CGFloat public let fontSize: CGFloat
public let textColor: UIColor public let textColor: UIColor
public let accentColor: UIColor
public let insets: UIEdgeInsets public let insets: UIEdgeInsets
public let hideKeyboard: Bool public let hideKeyboard: Bool
public let customInputView: UIView? public let customInputView: UIView?
@ -147,6 +148,7 @@ public final class TextFieldComponent: Component {
externalState: ExternalState, externalState: ExternalState,
fontSize: CGFloat, fontSize: CGFloat,
textColor: UIColor, textColor: UIColor,
accentColor: UIColor,
insets: UIEdgeInsets, insets: UIEdgeInsets,
hideKeyboard: Bool, hideKeyboard: Bool,
customInputView: UIView?, customInputView: UIView?,
@ -169,6 +171,7 @@ public final class TextFieldComponent: Component {
self.externalState = externalState self.externalState = externalState
self.fontSize = fontSize self.fontSize = fontSize
self.textColor = textColor self.textColor = textColor
self.accentColor = accentColor
self.insets = insets self.insets = insets
self.hideKeyboard = hideKeyboard self.hideKeyboard = hideKeyboard
self.customInputView = customInputView self.customInputView = customInputView
@ -205,6 +208,9 @@ public final class TextFieldComponent: Component {
if lhs.textColor != rhs.textColor { if lhs.textColor != rhs.textColor {
return false return false
} }
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.insets != rhs.insets { if lhs.insets != rhs.insets {
return false return false
} }
@ -308,13 +314,13 @@ public final class TextFieldComponent: Component {
let inputState = f(self.inputState) let inputState = f(self.inputState)
let currentAttributedText = self.textView.attributedText let currentAttributedText = self.textView.attributedText
let updatedAttributedText = textAttributedStringForStateText(inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.textColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) let updatedAttributedText = textAttributedStringForStateText(inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.accentColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
if currentAttributedText != updatedAttributedText { if currentAttributedText != updatedAttributedText {
self.textView.attributedText = updatedAttributedText self.textView.attributedText = updatedAttributedText
} }
self.textView.selectedRange = NSMakeRange(inputState.selectionRange.lowerBound, inputState.selectionRange.count) self.textView.selectedRange = NSMakeRange(inputState.selectionRange.lowerBound, inputState.selectionRange.count)
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
self.updateEntities() self.updateEntities()
@ -446,7 +452,7 @@ public final class TextFieldComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return return
} }
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize) refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
self.textView.updateTextContainerInset() self.textView.updateTextContainerInset()
@ -951,7 +957,7 @@ public final class TextFieldComponent: Component {
self.textView.isScrollEnabled = false self.textView.isScrollEnabled = false
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize) refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
if self.textView.subviews.count > 1, animated { if self.textView.subviews.count > 1, animated {

View File

@ -3284,7 +3284,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
if let node = node { if let node = node {
strongSelf.messageTooltipController?.dismiss() strongSelf.messageTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
let padding: CGFloat
let timeout: Double
let balancedTextLayout: Bool
if text.count > 140 {
timeout = 5.0
padding = 20.0
balancedTextLayout = true
} else {
timeout = 2.0
padding = 8.0
balancedTextLayout = false
}
let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: balancedTextLayout, isBlurred: true, timeout: timeout, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: padding)
strongSelf.messageTooltipController = tooltipController strongSelf.messageTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] _ in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController {