diff --git a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift index 46964fb42b..42998dbb3a 100644 --- a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift +++ b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift @@ -323,6 +323,7 @@ public final class ListComposePollOptionComponent: Component { externalState: component.externalState ?? TextFieldComponent.ExternalState(), fontSize: 17.0, textColor: component.theme.list.itemPrimaryTextColor, + accentColor: component.theme.list.itemPrimaryTextColor, insets: UIEdgeInsets(top: verticalInset, left: 8.0, bottom: verticalInset, right: 8.0), hideKeyboard: component.inputMode == .emoji, customInputView: nil, diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift index b190f3a0fe..86b92064a9 100644 --- a/submodules/Display/Source/TooltipControllerNode.swift +++ b/submodules/Display/Source/TooltipControllerNode.swift @@ -84,7 +84,7 @@ final class TooltipControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout - let maxWidth = layout.size.width - 20.0 + let maxWidth = layout.size.width - 20.0 - self.padding * 2.0 let contentSize: CGSize diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD index ba04d3acd3..f6555b701f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD @@ -16,7 +16,10 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", + "//submodules/TelegramStringFormatting", "//submodules/TextFormat", + "//submodules/Geocoding", + "//submodules/UrlEscaping", "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift index 38f5bdde5e..0e51e78b0a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift @@ -6,33 +6,67 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData +import TelegramStringFormatting import TextFormat import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon 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 { - private let backgroundView: MessageInlineBlockBackgroundView + private var backgroundView: MessageInlineBlockBackgroundView? private var titleNode: TextNode private var titleBadgeLabel: TextNode private var titleBadgeButton: HighlightTrackingButtonNode? + private let textClippingNode: ASDisplayNode 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 var isExpanded: Bool = false + private var appliedIsExpanded: Bool = false + required public init() { - self.backgroundView = MessageInlineBlockBackgroundView() - self.titleNode = TextNode() self.titleBadgeLabel = TextNode() + self.textClippingNode = ASDisplayNode() self.textNode = TextNode() + self.expandIcon = ASImageNode() self.statusNode = ChatMessageDateAndStatusNode() - + super.init() - self.view.addSubview(self.backgroundView) + self.textClippingNode.clipsToBounds = true + self.addSubnode(self.textClippingNode) self.titleNode.isUserInteractionEnabled = false self.titleNode.contentMode = .topLeft @@ -44,29 +78,145 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode self.textNode.contentMode = .topLeft self.textNode.contentsScale = UIScreenScale self.textNode.displaysAsynchronously = false - self.addSubnode(self.textNode) + self.textClippingNode.addSubnode(self.textNode) self.titleBadgeLabel.isUserInteractionEnabled = false self.titleBadgeLabel.contentMode = .topLeft self.titleBadgeLabel.contentsScale = UIScreenScale self.titleBadgeLabel.displaysAsynchronously = false self.addSubnode(self.titleBadgeLabel) + + self.expandIcon.displaysAsynchronously = false + self.addSubnode(self.expandIcon) } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + 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))) { let titleLayout = TextNode.asyncLayout(self.titleNode) let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel) let textLayout = TextNode.asyncLayout(self.textNode) + let measureTextLayout = TextNode.asyncLayout(nil) let statusLayout = self.statusNode.asyncLayout() + let currentIsExpanded = self.isExpanded + return { item, layoutConstants, _, _, _, _ in 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 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 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 (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) 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 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) var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? @@ -207,29 +381,52 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0) } let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right - suggestedBoundingWidth += sideInsets + suggestedBoundingWidth += (sideInsets - 2.0) * 2.0 return (suggestedBoundingWidth, { boundingWidth in var boundingSize: CGSize 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 { boundingSize.height += statusSizeAndApply.0.height } boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right 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 { + info?.setInvertOffsetDirection() + let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme 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 - if case .System = animation { + if case .System = animation, !isExpandedUpdated { if let cachedLayout = cachedLayout { if !cachedLayout.areLinesEqual(to: textLayout) { if let textContents = strongSelf.textNode.contents { @@ -238,7 +435,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode fadeNode.contents = textContents fadeNode.frame = strongSelf.textNode.frame 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?.removeFromSupernode() }) @@ -248,12 +445,77 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode } } - let _ = titleApply() - let _ = textApply() - let _ = titleBadgeApply() + 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() strongSelf.titleNode.frame = titleFrame - strongSelf.textNode.frame = textFrame + let _ = titleBadgeApply() + + let _ = textApply() + 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 if let firstLine = titleLayout.linesRects().first { @@ -269,45 +531,54 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode strongSelf.titleBadgeLabel.frame = titleBadgeFrame - let button: HighlightTrackingButtonNode + let titleBadgeButton: HighlightTrackingButtonNode if let current = strongSelf.titleBadgeButton { - button = current - button.bounds = CGRect(origin: .zero, size: badgeBackgroundFrame.size) - animation.animator.updatePosition(layer: button.layer, position: badgeBackgroundFrame.center, completion: nil) + titleBadgeButton = current + titleBadgeButton.bounds = CGRect(origin: .zero, size: badgeBackgroundFrame.size) + animation.animator.updatePosition(layer: titleBadgeButton.layer, position: badgeBackgroundFrame.center, completion: nil) } else { - button = HighlightTrackingButtonNode() - button.addTarget(self, action: #selector(strongSelf.badgePressed), forControlEvents: .touchUpInside) - button.frame = badgeBackgroundFrame - button.highligthedChanged = { [weak self, weak button] highlighted in - if let strongSelf = self, let button { + titleBadgeButton = HighlightTrackingButtonNode() + titleBadgeButton.addTarget(self, action: #selector(strongSelf.badgePressed), forControlEvents: .touchUpInside) + titleBadgeButton.frame = badgeBackgroundFrame + titleBadgeButton.highligthedChanged = { [weak self, weak titleBadgeButton] highlighted in + if let strongSelf = self, let titleBadgeButton { if highlighted { - button.layer.removeAnimation(forKey: "opacity") - button.alpha = 0.4 + titleBadgeButton.layer.removeAnimation(forKey: "opacity") + titleBadgeButton.alpha = 0.4 strongSelf.titleBadgeLabel.layer.removeAnimation(forKey: "opacity") strongSelf.titleBadgeLabel.alpha = 0.4 } else { - button.alpha = 1.0 - button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + titleBadgeButton.alpha = 1.0 + titleBadgeButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) strongSelf.titleBadgeLabel.alpha = 1.0 strongSelf.titleBadgeLabel.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } } } - strongSelf.titleBadgeButton = button - strongSelf.addSubnode(button) + strongSelf.titleBadgeButton = titleBadgeButton + strongSelf.addSubnode(titleBadgeButton) } - if themeUpdated || button.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) + if themeUpdated || titleBadgeButton.backgroundImage(for: .normal) == nil { + 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)) - strongSelf.backgroundView.frame = backgroundFrame - strongSelf.backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: .None) + animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: clippingTextFrame, completion: nil) + 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 { - 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 { strongSelf.addSubnode(strongSelf.statusNode) statusSizeAndApply.1(.None) diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift index b8fa043af9..758560fd94 100644 --- a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift @@ -211,6 +211,7 @@ private final class FactCheckAlertContentNode: AlertContentNode { externalState: self.inputFieldExternalState, fontSize: 14.0, textColor: self.presentationTheme.actionSheet.inputTextColor, + accentColor: self.presentationTheme.actionSheet.controlAccentColor, insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0), hideKeyboard: false, customInputView: nil, @@ -218,7 +219,7 @@ private final class FactCheckAlertContentNode: AlertContentNode { isOneLineWhenUnfocused: false, characterLimit: nil, emptyLineHandling: .oneConsecutive, - formatMenuAvailability: .available([.bold, .italic, .monospace, .link, .strikethrough, .underline]), + formatMenuAvailability: .available([.bold, .italic, .link]), returnKeyType: .done, lockedFormatAction: { }, diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift index 64e26e0594..1a9ccf535f 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift @@ -257,6 +257,7 @@ public final class ListMultilineTextFieldItemComponent: Component { externalState: self.textFieldExternalState, fontSize: 17.0, 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), hideKeyboard: false, customInputView: nil, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index f3f9a2697d..16102b6251 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -781,6 +781,7 @@ public final class MessageInputPanelComponent: Component { externalState: self.textFieldExternalState, fontSize: 17.0, textColor: UIColor(rgb: 0xffffff), + accentColor: UIColor(rgb: 0xffffff), insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0), hideKeyboard: component.hideKeyboard, customInputView: component.customInputView, diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 03fa474dba..d445bd96fc 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -124,6 +124,7 @@ public final class TextFieldComponent: Component { public let externalState: ExternalState public let fontSize: CGFloat public let textColor: UIColor + public let accentColor: UIColor public let insets: UIEdgeInsets public let hideKeyboard: Bool public let customInputView: UIView? @@ -147,6 +148,7 @@ public final class TextFieldComponent: Component { externalState: ExternalState, fontSize: CGFloat, textColor: UIColor, + accentColor: UIColor, insets: UIEdgeInsets, hideKeyboard: Bool, customInputView: UIView?, @@ -169,6 +171,7 @@ public final class TextFieldComponent: Component { self.externalState = externalState self.fontSize = fontSize self.textColor = textColor + self.accentColor = accentColor self.insets = insets self.hideKeyboard = hideKeyboard self.customInputView = customInputView @@ -205,6 +208,9 @@ public final class TextFieldComponent: Component { if lhs.textColor != rhs.textColor { return false } + if lhs.accentColor != rhs.accentColor { + return false + } if lhs.insets != rhs.insets { return false } @@ -308,13 +314,13 @@ public final class TextFieldComponent: Component { let inputState = f(self.inputState) 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 { self.textView.attributedText = updatedAttributedText } 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() @@ -446,7 +452,7 @@ public final class TextFieldComponent: Component { guard let component = self.component else { 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) self.textView.updateTextContainerInset() @@ -951,7 +957,7 @@ public final class TextFieldComponent: Component { 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) if self.textView.subviews.count > 1, animated { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ae9620db9d..7b1af42292 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3284,7 +3284,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { if let node = node { 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 tooltipController.dismissed = { [weak tooltipController] _ in if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController {