mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
043cdc33d5
6 changed files with 452 additions and 46 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue