Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2026-03-26 18:43:33 +01:00
commit 043cdc33d5
6 changed files with 452 additions and 46 deletions

View file

@ -45,3 +45,22 @@ open class SparseContainerView: UIView {
}
}
}
open class TransparentHitView: UIView {
public var onTap: (() -> Void)?
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let event {
var eventIsPresses = false
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
eventIsPresses = event.type == .presses
}
if event.type == .touches || eventIsPresses {
self.onTap?()
return nil
}
}
return super.hitTest(point, with: event)
}
}

View file

@ -56,12 +56,21 @@ private let expandableBlockMaskImage: UIImage = {
}()
private final class InteractiveTextNodeStrikethrough {
enum Style {
case single
case wavy
}
let range: NSRange
let frame: CGRect
init(range: NSRange, frame: CGRect) {
let color: UIColor?
let style: Style
init(range: NSRange, frame: CGRect, color: UIColor? = nil, style: Style = .single) {
self.range = range
self.frame = frame
self.color = color
self.style = style
}
}
@ -1814,11 +1823,11 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
line.strikethroughs.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height)))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] {
} else if let underlineStyle = attributes[NSAttributedString.Key.underlineStyle] as? Int {
let lowerX = floor(CTLineGetOffsetForStringIndex(line.line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
line.underlines.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height)))
line.underlines.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height), color: attributes[NSAttributedString.Key.underlineColor] as? UIColor, style: underlineStyle == NSUnderlineStyle.patternDot.rawValue ? .wavy : .single))
}
if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
@ -2518,11 +2527,46 @@ final class TextContentItemLayer: SimpleLayer {
textColor = color
}
}
if let textColor = textColor {
context.setFillColor(textColor.cgColor)
switch strikethrough.style {
case .single:
if let color = strikethrough.color {
context.setFillColor(color.cgColor)
} else if let textColor {
context.setFillColor(textColor.cgColor)
}
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.maxY - 2.0, width: frame.width, height: 1.0))
case .wavy:
if let color = strikethrough.color {
context.setStrokeColor(color.cgColor)
} else if let textColor {
context.setStrokeColor(textColor.cgColor)
}
context.setLineWidth(1.33)
context.setLineCap(.round)
context.setLineJoin(.round)
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.maxY + 12.0)
let amplitude: CGFloat = 1.2
let period: CGFloat = 8.0
let phase: CGFloat = -0.5
let midY = frame.midY
let step: CGFloat = 1.0
context.saveGState()
context.clip(to: frame)
var x = frame.minX
context.move(to: CGPoint(x: x, y: midY + amplitude * sin(phase)))
x += step
while x <= frame.maxX + step {
let y = midY + amplitude * sin((x - frame.minX) * 2.0 * .pi / period + phase)
context.addLine(to: CGPoint(x: x, y: y))
x += step
}
context.strokePath()
context.restoreGState()
}
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.maxY - 2.0, width: frame.width, height: 1.0))
}
}
}

View file

@ -368,3 +368,257 @@ public final class InteractiveTextNodeWithEntities {
}
}
}
public final class InteractiveTextComponent: Component {
public final class External {
public fileprivate(set) var layout: InteractiveTextNodeLayout?
public init() {
}
}
public let external: External?
public let attributedString: NSAttributedString?
public let backgroundColor: UIColor?
public let minimumNumberOfLines: Int
public let maximumNumberOfLines: Int
public let truncationType: CTLineTruncationType
public let alignment: NSTextAlignment
public let verticalAlignment: TextVerticalAlignment
public let lineSpacing: CGFloat
public let cutout: TextNodeCutout?
public let insets: UIEdgeInsets
public let lineColor: UIColor?
public let textShadowColor: UIColor?
public let textShadowBlur: CGFloat?
public let textStroke: (UIColor, CGFloat)?
public let displayContentsUnderSpoilers: Bool
public let customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?
public let expandedBlocks: Set<Int>
public let context: AccountContext
public let cache: AnimationCache
public let renderer: MultiAnimationRenderer
public let placeholderColor: UIColor
public let attemptSynchronous: Bool
public let textColor: UIColor
public let spoilerEffectColor: UIColor
public let spoilerTextColor: UIColor
public let areContentAnimationsEnabled: Bool
public let spoilerExpandPoint: CGPoint?
public let crossfadeContents: ((UIView) -> Void)?
public let canHandleTapAtPoint: ((CGPoint) -> Bool)?
public let requestToggleBlockCollapsed: ((Int) -> Void)?
public let requestDisplayContentsUnderSpoilers: ((CGPoint?) -> Void)?
public init(
external: External? = nil,
attributedString: NSAttributedString?,
backgroundColor: UIColor?,
minimumNumberOfLines: Int,
maximumNumberOfLines: Int,
truncationType: CTLineTruncationType,
alignment: NSTextAlignment,
verticalAlignment: TextVerticalAlignment,
lineSpacing: CGFloat,
cutout: TextNodeCutout?,
insets: UIEdgeInsets,
lineColor: UIColor?,
textShadowColor: UIColor?,
textShadowBlur: CGFloat?,
textStroke: (UIColor, CGFloat)?,
displayContentsUnderSpoilers: Bool,
customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?,
expandedBlocks: Set<Int>,
context: AccountContext,
cache: AnimationCache,
renderer: MultiAnimationRenderer,
placeholderColor: UIColor,
attemptSynchronous: Bool,
textColor: UIColor,
spoilerEffectColor: UIColor,
spoilerTextColor: UIColor,
areContentAnimationsEnabled: Bool,
spoilerExpandPoint: CGPoint?,
crossfadeContents: ((UIView) -> Void)? = nil,
canHandleTapAtPoint: ((CGPoint) -> Bool)? = nil,
requestToggleBlockCollapsed: ((Int) -> Void)? = nil,
requestDisplayContentsUnderSpoilers: ((CGPoint?) -> Void)? = nil
) {
self.external = external
self.attributedString = attributedString
self.backgroundColor = backgroundColor
self.minimumNumberOfLines = minimumNumberOfLines
self.maximumNumberOfLines = maximumNumberOfLines
self.truncationType = truncationType
self.alignment = alignment
self.verticalAlignment = verticalAlignment
self.lineSpacing = lineSpacing
self.cutout = cutout
self.insets = insets
self.lineColor = lineColor
self.textShadowColor = textShadowColor
self.textShadowBlur = textShadowBlur
self.textStroke = textStroke
self.displayContentsUnderSpoilers = displayContentsUnderSpoilers
self.customTruncationToken = customTruncationToken
self.expandedBlocks = expandedBlocks
self.context = context
self.cache = cache
self.renderer = renderer
self.placeholderColor = placeholderColor
self.attemptSynchronous = attemptSynchronous
self.textColor = textColor
self.spoilerTextColor = spoilerTextColor
self.areContentAnimationsEnabled = areContentAnimationsEnabled
self.spoilerExpandPoint = spoilerExpandPoint
self.crossfadeContents = crossfadeContents
self.spoilerEffectColor = spoilerEffectColor
self.canHandleTapAtPoint = canHandleTapAtPoint
self.requestToggleBlockCollapsed = requestToggleBlockCollapsed
self.requestDisplayContentsUnderSpoilers = requestDisplayContentsUnderSpoilers
}
public static func ==(lhs: InteractiveTextComponent, rhs: InteractiveTextComponent) -> Bool {
if lhs.external !== rhs.external {
return false
}
if lhs.attributedString != rhs.attributedString {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.minimumNumberOfLines != rhs.minimumNumberOfLines {
return false
}
if lhs.maximumNumberOfLines != rhs.maximumNumberOfLines {
return false
}
if lhs.truncationType != rhs.truncationType {
return false
}
if lhs.alignment != rhs.alignment {
return false
}
if lhs.verticalAlignment != rhs.verticalAlignment {
return false
}
if lhs.lineSpacing != rhs.lineSpacing {
return false
}
if lhs.cutout != rhs.cutout {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.lineColor != rhs.lineColor {
return false
}
if lhs.textShadowColor != rhs.textShadowColor {
return false
}
if lhs.textShadowBlur != rhs.textShadowBlur {
return false
}
if lhs.textStroke?.0 != rhs.textStroke?.0 || lhs.textStroke?.1 != rhs.textStroke?.1 {
return false
}
if lhs.displayContentsUnderSpoilers != rhs.displayContentsUnderSpoilers {
return false
}
if (lhs.customTruncationToken == nil) != (rhs.customTruncationToken == nil) {
return false
}
if lhs.expandedBlocks != rhs.expandedBlocks {
return false
}
if lhs.spoilerExpandPoint != rhs.spoilerExpandPoint {
return false
}
return true
}
public final class View: UIView {
private let textNodeWithEntities: InteractiveTextNodeWithEntities
public var textNode: TextNodeProtocol {
return self.textNodeWithEntities.textNode
}
override public init(frame: CGRect) {
self.textNodeWithEntities = InteractiveTextNodeWithEntities()
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: InteractiveTextComponent, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let makeLayout = InteractiveTextNodeWithEntities.asyncLayout(self.textNodeWithEntities)
let (layout, apply) = makeLayout(InteractiveTextNodeLayoutArguments(attributedString: component.attributedString, backgroundColor: component.backgroundColor, minimumNumberOfLines: component.minimumNumberOfLines, maximumNumberOfLines: component.maximumNumberOfLines, truncationType: component.truncationType, constrainedSize: availableSize, alignment: component.alignment, verticalAlignment: component.verticalAlignment, lineSpacing: component.lineSpacing, cutout: component.cutout, insets: component.insets, lineColor: component.lineColor, textShadowColor: component.textShadowColor, textShadowBlur: component.textShadowBlur, textStroke: component.textStroke, displayContentsUnderSpoilers: component.displayContentsUnderSpoilers, customTruncationToken: component.customTruncationToken, expandedBlocks: component.expandedBlocks))
let textFrame = CGRect(origin: CGPoint(), size: layout.size)
var spoilerExpandRect: CGRect?
if let mappedLocation = component.spoilerExpandPoint {
let getDistance: (CGPoint, CGPoint) -> CGFloat = { a, b in
let v = CGPoint(x: a.x - b.x, y: a.y - b.y)
return sqrt(v.x * v.x + v.y * v.y)
}
var maxDistance: CGFloat = getDistance(mappedLocation, CGPoint(x: 0.0, y: 0.0))
maxDistance = max(maxDistance, getDistance(mappedLocation, CGPoint(x: textFrame.width, y: 0.0)))
maxDistance = max(maxDistance, getDistance(mappedLocation, CGPoint(x: textFrame.width, y: textFrame.height)))
maxDistance = max(maxDistance, getDistance(mappedLocation, CGPoint(x: 0.0, y: textFrame.height)))
let mappedSize = CGSize(width: maxDistance * 2.0, height: maxDistance * 2.0)
spoilerExpandRect = mappedSize.centered(around: mappedLocation)
}
let applyArguments = InteractiveTextNode.ApplyArguments(
animation: transition.animation.isImmediate ? .None : .System(duration: 0.4, transition: .init(duration: 0.4, curve: .spring, interactive: false)),
spoilerTextColor: component.spoilerTextColor,
spoilerEffectColor: component.spoilerEffectColor,
areContentAnimationsEnabled: component.areContentAnimationsEnabled,
spoilerExpandRect: spoilerExpandRect,
crossfadeContents: component.crossfadeContents
)
let resultNode = apply(InteractiveTextNodeWithEntities.Arguments(
context: component.context,
cache: component.cache,
renderer: component.renderer,
placeholderColor: component.placeholderColor,
attemptSynchronous: component.attemptSynchronous,
textColor: component.textColor,
spoilerEffectColor: component.spoilerEffectColor,
applyArguments: applyArguments
))
if self.textNodeWithEntities.textNode.view.superview == nil {
self.addSubview(self.textNodeWithEntities.textNode.view)
}
resultNode.visibilityRect = CGRect(origin: CGPoint(), size: layout.size)
resultNode.textNode.view.frame = textFrame
resultNode.textNode.canHandleTapAtPoint = component.canHandleTapAtPoint
resultNode.textNode.requestToggleBlockCollapsed = component.requestToggleBlockCollapsed
resultNode.textNode.requestDisplayContentsUnderSpoilers = component.requestDisplayContentsUnderSpoilers
component.external?.layout = resultNode.textNode.cachedLayout
return layout.size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -37,6 +37,7 @@ swift_library(
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramUI/Components/TooltipComponent",
"//submodules/TelegramUI/Components/ToastComponent",
"//submodules/TelegramUI/Components/InteractiveTextComponent",
"//submodules/TelegramNotices",
"//submodules/Markdown",
"//submodules/TelegramUIPreferences",

View file

@ -8,7 +8,6 @@ import AccountContext
import MultilineTextComponent
import BundleIconComponent
import TelegramCore
import MultilineTextWithEntitiesComponent
import TextFormat
import PlainButtonComponent
import CheckComponent
@ -16,6 +15,7 @@ import ShimmerEffect
import TextSelectionNode
import Pasteboard
import Speak
import InteractiveTextComponent
final class TextProcessingTextAreaComponent: Component {
let context: AccountContext
@ -116,7 +116,7 @@ final class TextProcessingTextAreaComponent: Component {
private var emojify: ComponentView<Empty>?
private let titleButton: HighlightTrackingButton
private let textState = MultilineTextWithEntitiesComponent.External()
private let textState = InteractiveTextComponent.External()
private let textContainer: UIView
private let text = ComponentView<Empty>()
private var expandShadow: UIImageView?
@ -126,7 +126,7 @@ final class TextProcessingTextAreaComponent: Component {
private var previousText: TextWithEntities?
private var previousTextLineCount: Int?
private let measureLoadingTextState = MultilineTextWithEntitiesComponent.External()
private let measureLoadingTextState = InteractiveTextComponent.External()
private let measureLoadingText = ComponentView<Empty>()
private var shimmerEffectNode: ShimmerEffectNode?
@ -135,6 +135,10 @@ final class TextProcessingTextAreaComponent: Component {
private let textSelectionKnobContainer: UIView
private let textSelectionKnobSurface: UIView
private var expandedBlockIds: Set<Int> = Set()
private var appliedExpandedBlockIds: Set<Int>?
private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil)
private var currentSpeechHolder: SpeechSynthesizerHolder?
override init(frame: CGRect) {
@ -181,6 +185,25 @@ final class TextProcessingTextAreaComponent: Component {
}
component.titleAction?(titleView)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if result == self.textSelectionNode?.view && !self.displayContentsUnderSpoilers.value {
if let textView = self.text.view as? InteractiveTextComponent.View {
let textPoint = self.convert(point, to: textView.textNode.view)
if let attributes = textView.textNode.attributesAtPoint(textPoint, orNearest: false)?.1 {
if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil {
if let value = textView.textNode.view.hitTest(textPoint, with: event) {
return value
}
}
}
}
}
return result
}
func update(component: TextProcessingTextAreaComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
@ -362,27 +385,80 @@ final class TextProcessingTextAreaComponent: Component {
], range: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound))
}
}
let textOrigin = CGPoint(x: sideInset, y: contentHeight)
var spoilerExpandPoint: CGPoint?
if let location = self.displayContentsUnderSpoilers.location {
self.displayContentsUnderSpoilers.location = nil
spoilerExpandPoint = CGPoint(x: location.x - textOrigin.x, y: location.y - textOrigin.y)
}
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent(
transition: transition,
component: AnyComponent(InteractiveTextComponent(
external: self.textState,
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
text: .plain(textValue),
attributedString: textValue,
backgroundColor: nil,
minimumNumberOfLines: 1,
maximumNumberOfLines: 0,
truncationType: .end,
alignment: .left,
verticalAlignment: .top,
lineSpacing: 0.12,
cutout: nil,
insets: UIEdgeInsets(),
spoilerColor: component.theme.list.itemPrimaryTextColor,
enableLooping: true,
displaysAsynchronously: false,
lineColor: nil,
textShadowColor: nil,
textShadowBlur: nil,
textStroke: nil,
displayContentsUnderSpoilers: self.displayContentsUnderSpoilers.value,
customTruncationToken: nil,
expandedBlocks: Set(),
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
attemptSynchronous: true,
textColor: component.theme.list.itemPrimaryTextColor,
spoilerEffectColor: component.theme.list.itemPrimaryTextColor,
spoilerTextColor: component.theme.list.itemPrimaryTextColor,
areContentAnimationsEnabled: true,
spoilerExpandPoint: spoilerExpandPoint,
canHandleTapAtPoint: { _ in
return true
},
requestToggleBlockCollapsed: { [weak self] blockId in
guard let self else {
return
}
if self.expandedBlockIds.contains(blockId) {
self.expandedBlockIds.remove(blockId)
} else {
self.expandedBlockIds.insert(blockId)
}
self.state?.updated(transition: .spring(duration: 0.4))
},
requestDisplayContentsUnderSpoilers: { [weak self] location in
guard let self, let textView = self.text.view as? InteractiveTextComponent.View else {
return
}
cancelParentGestures(view: self)
var mappedLocation: CGPoint?
if let location {
mappedLocation = textView.textNode.layer.convert(location, to: self.layer)
}
self.displayContentsUnderSpoilers = (true, mappedLocation)
self.state?.updated(transition: .spring(duration: 0.4))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: textSize)
let textFrame = CGRect(origin: textOrigin, size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
@ -400,7 +476,7 @@ final class TextProcessingTextAreaComponent: Component {
var textContainerFrame = textFrame
if let isExpanded = component.isExpanded, let textLayout = self.textState.layout, textLayout.numberOfLines > 1 {
if !isExpanded.value, let firstLineRect = textLayout.linesRects().first {
textContainerFrame.size.height = firstLineRect.maxY - 14.0
textContainerFrame.size.height = ceil(firstLineRect.maxY)
}
let expandButton: ComponentView<Empty>
@ -482,13 +558,13 @@ final class TextProcessingTextAreaComponent: Component {
}
if component.copyAction != nil, let textLayout = self.textState.layout {
if textLayout.trailingLineWidth >= availableSize.width - sideInset - 32.0 {
if textLayout.trailingLineWidth >= availableSize.width - sideInset - 32.0 || textLayout.trailingLineIsRTL {
textContainerFrame.size.height += 28.0
}
}
}
if component.text != nil, component.isExpanded?.value ?? true, let textView = self.text.view as? MultilineTextWithEntitiesComponent.View {
if component.text != nil, component.isExpanded?.value ?? true, let textView = self.text.view as? InteractiveTextComponent.View {
let textSelectionNode: TextSelectionNode
if let current = self.textSelectionNode {
textSelectionNode = current
@ -611,20 +687,35 @@ final class TextProcessingTextAreaComponent: Component {
let measureLoadingTextSize = self.measureLoadingText.update(
transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent(
component: AnyComponent(InteractiveTextComponent(
external: self.measureLoadingTextState,
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
text: .plain(NSAttributedString(string: fakeLines, font: Font.regular(fontSize), textColor: .black)),
attributedString: NSAttributedString(string: fakeLines, font: Font.regular(fontSize), textColor: .black),
backgroundColor: nil,
minimumNumberOfLines: 1,
maximumNumberOfLines: 0,
truncationType: .end,
alignment: .left,
verticalAlignment: .top,
lineSpacing: 0.12,
cutout: nil,
insets: UIEdgeInsets(),
spoilerColor: component.theme.list.itemPrimaryTextColor,
enableLooping: true,
displaysAsynchronously: false,
lineColor: nil,
textShadowColor: nil,
textShadowBlur: nil,
textStroke: nil,
displayContentsUnderSpoilers: true,
customTruncationToken: nil,
expandedBlocks: Set(),
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
attemptSynchronous: true,
textColor: component.theme.list.itemPrimaryTextColor,
spoilerEffectColor: component.theme.list.itemPrimaryTextColor,
spoilerTextColor: component.theme.list.itemPrimaryTextColor,
areContentAnimationsEnabled: true,
spoilerExpandPoint: nil
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)

View file

@ -229,16 +229,6 @@ final class TextProcessingTranslateContentComponent: Component {
}
}
}
@objc private func onTooltipTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
component.externalState.displayStyleTooltip = false
self.state?.updated(transition: .easeInOut(duration: 0.2))
}
}
func update(component: TextProcessingTranslateContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
@ -488,8 +478,15 @@ final class TextProcessingTranslateContentComponent: Component {
} else {
tooltipTransition = tooltipTransition.withAnimation(.none)
tooltip = ComponentView()
dimView = UIView()
dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTooltipTapGesture(_:))))
let dimViewValue = TransparentHitView()
dimViewValue.onTap = { [weak self] in
guard let self, let component = self.component else {
return
}
component.externalState.displayStyleTooltip = false
self.state?.updated(transition: .easeInOut(duration: 0.2))
}
dimView = dimViewValue
self.styleTooltip = (dimView, tooltip)
}
let tooltipSize = tooltip.update(