mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Collapsible quote improvements
This commit is contained in:
parent
b90e3563a3
commit
97bbf3ee0d
@ -550,7 +550,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
attributedText = updatedString
|
attributedText = updatedString
|
||||||
}
|
}
|
||||||
|
|
||||||
var customTruncationToken: NSAttributedString?
|
var customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?
|
||||||
var maximumNumberOfLines: Int = 0
|
var maximumNumberOfLines: Int = 0
|
||||||
if item.presentationData.isPreview {
|
if item.presentationData.isPreview {
|
||||||
if item.message.groupingKey != nil {
|
if item.message.groupingKey != nil {
|
||||||
@ -573,10 +573,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
maximumNumberOfLines = 12
|
maximumNumberOfLines = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
let truncationToken = NSMutableAttributedString()
|
let truncationTokenText = item.presentationData.strings.Conversation_ReadMore
|
||||||
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: textFont, textColor: messageTheme.primaryTextColor))
|
customTruncationToken = { baseFont, isQuote in
|
||||||
truncationToken.append(NSAttributedString(string: item.presentationData.strings.Conversation_ReadMore, font: textFont, textColor: messageTheme.accentTextColor))
|
let truncationToken = NSMutableAttributedString()
|
||||||
customTruncationToken = truncationToken
|
if isQuote {
|
||||||
|
truncationToken.append(NSAttributedString(string: "\u{2026}", font: Font.regular(baseFont.pointSize), textColor: messageTheme.primaryTextColor))
|
||||||
|
} else {
|
||||||
|
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(baseFont.pointSize), textColor: messageTheme.primaryTextColor))
|
||||||
|
truncationToken.append(NSAttributedString(string: truncationTokenText, font: Font.regular(baseFont.pointSize), textColor: messageTheme.accentTextColor))
|
||||||
|
}
|
||||||
|
return truncationToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
|
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
|
||||||
@ -1416,7 +1423,6 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.displayContentsUnderSpoilers = (value, location)
|
self.displayContentsUnderSpoilers = (value, location)
|
||||||
//self.displayContentsUnderSpoilers.location = nil
|
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,19 @@ private func generateBlockMaskImage() -> UIImage {
|
|||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||||
let colors: [CGColor] = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor]
|
var colors: [CGColor] = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor]
|
||||||
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
var gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width - 20.0, y: size.height), startRadius: 0.0, endCenter: CGPoint(x: size.width - 20.0, y: size.height), endRadius: 34.0, options: CGGradientDrawingOptions())
|
context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width - 20.0, y: size.height), startRadius: 0.0, endCenter: CGPoint(x: size.width - 20.0, y: size.height), endRadius: 34.0, options: CGGradientDrawingOptions())
|
||||||
|
|
||||||
|
locations = [0.0, 0.4, 1.0]
|
||||||
|
colors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor]
|
||||||
|
gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.setBlendMode(.destinationIn)
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: 0.0, y: size.height - 8.0), options: CGGradientDrawingOptions())
|
||||||
})!.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: size.height - 1.0, right: size.width - 1.0), resizingMode: .stretch)
|
})!.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: size.height - 1.0, right: size.width - 1.0), resizingMode: .stretch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +103,9 @@ private final class InteractiveTextNodeAttachment {
|
|||||||
|
|
||||||
private final class InteractiveTextNodeLine {
|
private final class InteractiveTextNodeLine {
|
||||||
let line: CTLine
|
let line: CTLine
|
||||||
|
let constrainedWidth: CGFloat
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
|
let intrinsicWidth: CGFloat
|
||||||
let ascent: CGFloat
|
let ascent: CGFloat
|
||||||
let descent: CGFloat
|
let descent: CGFloat
|
||||||
let range: NSRange?
|
let range: NSRange?
|
||||||
@ -108,9 +117,11 @@ private final class InteractiveTextNodeLine {
|
|||||||
var attachments: [InteractiveTextNodeAttachment]
|
var attachments: [InteractiveTextNodeAttachment]
|
||||||
let additionalTrailingLine: (CTLine, Double)?
|
let additionalTrailingLine: (CTLine, Double)?
|
||||||
|
|
||||||
init(line: CTLine, frame: CGRect, ascent: CGFloat, descent: CGFloat, range: NSRange?, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) {
|
init(line: CTLine, constrainedWidth: CGFloat, frame: CGRect, intrinsicWidth: CGFloat, ascent: CGFloat, descent: CGFloat, range: NSRange?, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) {
|
||||||
self.line = line
|
self.line = line
|
||||||
|
self.constrainedWidth = constrainedWidth
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
self.intrinsicWidth = intrinsicWidth
|
||||||
self.ascent = ascent
|
self.ascent = ascent
|
||||||
self.descent = descent
|
self.descent = descent
|
||||||
self.range = range
|
self.range = range
|
||||||
@ -269,7 +280,7 @@ public final class InteractiveTextNodeLayoutArguments {
|
|||||||
public let textShadowBlur: CGFloat?
|
public let textShadowBlur: CGFloat?
|
||||||
public let textStroke: (UIColor, CGFloat)?
|
public let textStroke: (UIColor, CGFloat)?
|
||||||
public let displayContentsUnderSpoilers: Bool
|
public let displayContentsUnderSpoilers: Bool
|
||||||
public let customTruncationToken: NSAttributedString?
|
public let customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?
|
||||||
public let expandedBlocks: Set<Int>
|
public let expandedBlocks: Set<Int>
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -289,7 +300,7 @@ public final class InteractiveTextNodeLayoutArguments {
|
|||||||
textShadowBlur: CGFloat? = nil,
|
textShadowBlur: CGFloat? = nil,
|
||||||
textStroke: (UIColor, CGFloat)? = nil,
|
textStroke: (UIColor, CGFloat)? = nil,
|
||||||
displayContentsUnderSpoilers: Bool = false,
|
displayContentsUnderSpoilers: Bool = false,
|
||||||
customTruncationToken: NSAttributedString? = nil,
|
customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)? = nil,
|
||||||
expandedBlocks: Set<Int> = Set()
|
expandedBlocks: Set<Int> = Set()
|
||||||
) {
|
) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
@ -1256,7 +1267,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
textShadowBlur: CGFloat?,
|
textShadowBlur: CGFloat?,
|
||||||
textStroke: (UIColor, CGFloat)?,
|
textStroke: (UIColor, CGFloat)?,
|
||||||
displayContentsUnderSpoilers: Bool,
|
displayContentsUnderSpoilers: Bool,
|
||||||
customTruncationToken: NSAttributedString?,
|
customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?,
|
||||||
expandedBlocks: Set<Int>
|
expandedBlocks: Set<Int>
|
||||||
) -> InteractiveTextNodeLayout {
|
) -> InteractiveTextNodeLayout {
|
||||||
let blockQuoteLeftInset: CGFloat = 9.0
|
let blockQuoteLeftInset: CGFloat = 9.0
|
||||||
@ -1348,7 +1359,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CalculatedSegment {
|
class CalculatedSegment {
|
||||||
var titleLine: InteractiveTextNodeLine?
|
var titleLine: InteractiveTextNodeLine?
|
||||||
var lines: [InteractiveTextNodeLine] = []
|
var lines: [InteractiveTextNodeLine] = []
|
||||||
var tintColor: UIColor?
|
var tintColor: UIColor?
|
||||||
@ -1356,16 +1367,18 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
var tertiaryTintColor: UIColor?
|
var tertiaryTintColor: UIColor?
|
||||||
var blockQuote: TextNodeBlockQuoteData?
|
var blockQuote: TextNodeBlockQuoteData?
|
||||||
var additionalWidth: CGFloat = 0.0
|
var additionalWidth: CGFloat = 0.0
|
||||||
|
|
||||||
|
init() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var calculatedSegments: [CalculatedSegment] = []
|
var calculatedSegments: [CalculatedSegment] = []
|
||||||
|
var remainingLines = maximumNumberOfLines <= 0 ? Int.max : maximumNumberOfLines
|
||||||
|
|
||||||
for segment in stringSegments {
|
for segment in stringSegments {
|
||||||
var calculatedSegment = CalculatedSegment()
|
if remainingLines <= 0 {
|
||||||
calculatedSegment.blockQuote = segment.blockQuote
|
break
|
||||||
calculatedSegment.tintColor = segment.tintColor
|
}
|
||||||
calculatedSegment.secondaryTintColor = segment.secondaryTintColor
|
|
||||||
calculatedSegment.tertiaryTintColor = segment.tertiaryTintColor
|
|
||||||
|
|
||||||
let rawSubstring = segment.substring.string as NSString
|
let rawSubstring = segment.substring.string as NSString
|
||||||
let substringLength = rawSubstring.length
|
let substringLength = rawSubstring.length
|
||||||
@ -1376,6 +1389,12 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
var currentLineStartIndex = segment.firstCharacterOffset
|
var currentLineStartIndex = segment.firstCharacterOffset
|
||||||
let segmentEndIndex = segment.firstCharacterOffset + substringLength
|
let segmentEndIndex = segment.firstCharacterOffset + substringLength
|
||||||
|
|
||||||
|
let calculatedSegment = CalculatedSegment()
|
||||||
|
calculatedSegment.blockQuote = segment.blockQuote
|
||||||
|
calculatedSegment.tintColor = segment.tintColor
|
||||||
|
calculatedSegment.secondaryTintColor = segment.secondaryTintColor
|
||||||
|
calculatedSegment.tertiaryTintColor = segment.tertiaryTintColor
|
||||||
|
|
||||||
var constrainedSegmentWidth = constrainedSize.width
|
var constrainedSegmentWidth = constrainedSize.width
|
||||||
var additionalOffsetX: CGFloat = 0.0
|
var additionalOffsetX: CGFloat = 0.0
|
||||||
if segment.blockQuote != nil {
|
if segment.blockQuote != nil {
|
||||||
@ -1398,13 +1417,16 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
|
|
||||||
if let title = segment.title {
|
if let title = segment.title {
|
||||||
let rawTitleLine = CTLineCreateWithAttributedString(title)
|
let rawTitleLine = CTLineCreateWithAttributedString(title)
|
||||||
if let titleLine = CTLineCreateTruncatedLine(rawTitleLine, constrainedSegmentWidth - additionalSegmentRightInset, .end, nil) {
|
let constrainedLineWidth = constrainedSegmentWidth - additionalSegmentRightInset
|
||||||
|
if let titleLine = CTLineCreateTruncatedLine(rawTitleLine, constrainedLineWidth, .end, nil) {
|
||||||
var lineAscent: CGFloat = 0.0
|
var lineAscent: CGFloat = 0.0
|
||||||
var lineDescent: CGFloat = 0.0
|
var lineDescent: CGFloat = 0.0
|
||||||
let lineWidth = CTLineGetTypographicBounds(titleLine, &lineAscent, &lineDescent, nil)
|
let lineWidth = CTLineGetTypographicBounds(titleLine, &lineAscent, &lineDescent, nil)
|
||||||
calculatedSegment.titleLine = InteractiveTextNodeLine(
|
calculatedSegment.titleLine = InteractiveTextNodeLine(
|
||||||
line: titleLine,
|
line: titleLine,
|
||||||
|
constrainedWidth: constrainedLineWidth,
|
||||||
frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)),
|
frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)),
|
||||||
|
intrinsicWidth: lineWidth,
|
||||||
ascent: lineAscent,
|
ascent: lineAscent,
|
||||||
descent: lineDescent,
|
descent: lineDescent,
|
||||||
range: nil,
|
range: nil,
|
||||||
@ -1421,7 +1443,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
}
|
}
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, currentLineStartIndex, constrainedSegmentWidth - additionalSegmentRightInset)
|
let constrainedLineWidth = constrainedSegmentWidth - additionalSegmentRightInset
|
||||||
|
let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, currentLineStartIndex, constrainedLineWidth)
|
||||||
|
|
||||||
if lineCharacterCount != 0 {
|
if lineCharacterCount != 0 {
|
||||||
let line = CTTypesetterCreateLine(typesetter, CFRange(location: currentLineStartIndex, length: lineCharacterCount))
|
let line = CTTypesetterCreateLine(typesetter, CFRange(location: currentLineStartIndex, length: lineCharacterCount))
|
||||||
@ -1441,7 +1464,9 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
|
|
||||||
calculatedSegment.lines.append(InteractiveTextNodeLine(
|
calculatedSegment.lines.append(InteractiveTextNodeLine(
|
||||||
line: line,
|
line: line,
|
||||||
|
constrainedWidth: constrainedLineWidth,
|
||||||
frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)),
|
frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)),
|
||||||
|
intrinsicWidth: lineWidth,
|
||||||
ascent: lineAscent,
|
ascent: lineAscent,
|
||||||
descent: lineDescent,
|
descent: lineDescent,
|
||||||
range: NSRange(location: currentLineStartIndex, length: lineCharacterCount),
|
range: NSRange(location: currentLineStartIndex, length: lineCharacterCount),
|
||||||
@ -1453,6 +1478,11 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
attachments: [],
|
attachments: [],
|
||||||
additionalTrailingLine: nil
|
additionalTrailingLine: nil
|
||||||
))
|
))
|
||||||
|
|
||||||
|
remainingLines -= 1
|
||||||
|
if remainingLines <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
additionalSegmentRightInset = 0.0
|
additionalSegmentRightInset = 0.0
|
||||||
@ -1462,11 +1492,62 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
if currentLineStartIndex >= segmentEndIndex {
|
if currentLineStartIndex >= segmentEndIndex {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if remainingLines <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedSegments.append(calculatedSegment)
|
calculatedSegments.append(calculatedSegment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remainingLines <= 0, let lastSegment = calculatedSegments.last, let lastLine = lastSegment.lines.last, let lineRange = lastLine.range, let lineFont = attributedString.attribute(.font, at: lineRange.lowerBound, effectiveRange: nil) as? UIFont {
|
||||||
|
let truncatedTokenString: NSAttributedString
|
||||||
|
if let customTruncationTokenValue = customTruncationToken?(lineFont, lastSegment.blockQuote != nil) {
|
||||||
|
if lineRange.length == 0 && customTruncationTokenValue.string.hasPrefix("\u{2026} ") {
|
||||||
|
truncatedTokenString = customTruncationTokenValue.attributedSubstring(from: NSRange(location: 2, length: customTruncationTokenValue.length - 2))
|
||||||
|
} else {
|
||||||
|
truncatedTokenString = customTruncationTokenValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:]
|
||||||
|
truncationTokenAttributes[NSAttributedString.Key.font] = lineFont
|
||||||
|
truncationTokenAttributes[NSAttributedString.Key(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber
|
||||||
|
let tokenString = "\u{2026}"
|
||||||
|
|
||||||
|
truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
||||||
|
|
||||||
|
var truncationTokenAscent: CGFloat = 0.0
|
||||||
|
var truncationTokenDescent: CGFloat = 0.0
|
||||||
|
let truncationTokenWidth = CTLineGetTypographicBounds(truncationToken, &truncationTokenAscent, &truncationTokenDescent, nil)
|
||||||
|
|
||||||
|
if let updatedLine = CTLineCreateTruncatedLine(lastLine.line, max(0.0, lastLine.constrainedWidth - truncationTokenWidth), .end, nil) {
|
||||||
|
var lineAscent: CGFloat = 0.0
|
||||||
|
var lineDescent: CGFloat = 0.0
|
||||||
|
var lineWidth = CTLineGetTypographicBounds(updatedLine, &lineAscent, &lineDescent, nil)
|
||||||
|
lineWidth = min(lineWidth, lastLine.constrainedWidth)
|
||||||
|
|
||||||
|
lastSegment.lines[lastSegment.lines.count - 1] = InteractiveTextNodeLine(
|
||||||
|
line: updatedLine,
|
||||||
|
constrainedWidth: lastLine.constrainedWidth,
|
||||||
|
frame: CGRect(origin: lastLine.frame.origin, size: CGSize(width: lineWidth, height: lineAscent + lineDescent)),
|
||||||
|
intrinsicWidth: lineWidth,
|
||||||
|
ascent: lineAscent,
|
||||||
|
descent: lineDescent,
|
||||||
|
range: lastLine.range,
|
||||||
|
isRTL: lastLine.isRTL,
|
||||||
|
strikethroughs: [],
|
||||||
|
spoilers: [],
|
||||||
|
spoilerWords: [],
|
||||||
|
embeddedItems: [],
|
||||||
|
attachments: [],
|
||||||
|
additionalTrailingLine: (truncationToken, 0.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var size = CGSize()
|
var size = CGSize()
|
||||||
let isTruncated = false
|
let isTruncated = false
|
||||||
|
|
||||||
@ -1642,8 +1723,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
}
|
}
|
||||||
|
|
||||||
var segmentBlockQuote: InteractiveTextNodeBlockQuote?
|
var segmentBlockQuote: InteractiveTextNodeBlockQuote?
|
||||||
if let blockQuote = segment.blockQuote, let tintColor = segment.tintColor, let blockIndex {
|
if let blockQuote = segment.blockQuote, let tintColor = segment.tintColor, let blockIndex, let firstLine = segment.lines.first, let lastLine = segment.lines.last {
|
||||||
segmentBlockQuote = InteractiveTextNodeBlockQuote(id: blockIndex, frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 3.0)), data: blockQuote, tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor, backgroundColor: blockQuote.backgroundColor, isCollapsed: (blockQuote.isCollapsible && segmentLines.count > 3) ? isCollapsed : nil)
|
segmentBlockQuote = InteractiveTextNodeBlockQuote(id: blockIndex, frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY + floor(0.15 * firstLine.frame.height)), size: CGSize(width: blockWidth, height: blockMaxY - blockMinY + floor(0.4 * lastLine.frame.height))), data: blockQuote, tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor, backgroundColor: blockQuote.backgroundColor, isCollapsed: (blockQuote.isCollapsible && segmentLines.count > 3) ? isCollapsed : nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
segments.append(InteractiveTextNodeSegment(
|
segments.append(InteractiveTextNodeSegment(
|
||||||
@ -1692,7 +1773,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: NSAttributedString?, expandedBlocks: Set<Int>) -> InteractiveTextNodeLayout {
|
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, expandedBlocks: Set<Int>) -> InteractiveTextNodeLayout {
|
||||||
guard let attributedString else {
|
guard let attributedString else {
|
||||||
return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks)
|
return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks)
|
||||||
}
|
}
|
||||||
@ -2171,7 +2252,7 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let (additionalTrailingLine, _) = line.additionalTrailingLine {
|
if let (additionalTrailingLine, _) = line.additionalTrailingLine {
|
||||||
context.textPosition = CGPoint(x: lineFrame.maxX, y: lineFrame.minY)
|
context.textPosition = CGPoint(x: lineFrame.minX + line.intrinsicWidth, y: lineFrame.maxY - line.descent)
|
||||||
|
|
||||||
let glyphRuns = CTLineGetGlyphRuns(additionalTrailingLine) as NSArray
|
let glyphRuns = CTLineGetGlyphRuns(additionalTrailingLine) as NSArray
|
||||||
if glyphRuns.count != 0 {
|
if glyphRuns.count != 0 {
|
||||||
@ -2302,25 +2383,28 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
blockExpandArrow.layerTintColor = blockQuote.tintColor.cgColor
|
blockExpandArrow.layerTintColor = blockQuote.tintColor.cgColor
|
||||||
|
|
||||||
let blockBackgroundFrame = blockQuote.frame.offsetBy(dx: params.item.contentOffset.x, dy: params.item.contentOffset.y + 4.0)
|
let blockBackgroundFrame = blockQuote.frame.offsetBy(dx: params.item.contentOffset.x, dy: params.item.contentOffset.y)
|
||||||
|
|
||||||
if animation.isAnimated {
|
if animation.isAnimated {
|
||||||
self.isAnimating = true
|
if blockBackgroundFrame != blockBackgroundView.layer.frame {
|
||||||
self.currentAnimationId += 1
|
self.isAnimating = true
|
||||||
let animationId = self.currentAnimationId
|
self.currentAnimationId += 1
|
||||||
animation.animator.updateFrame(layer: blockBackgroundView.layer, frame: blockBackgroundFrame, completion: { [weak self] completed in
|
let animationId = self.currentAnimationId
|
||||||
guard completed, let self, self.currentAnimationId == animationId, let params = self.params else {
|
|
||||||
return
|
animation.animator.updateFrame(layer: blockBackgroundView.layer, frame: blockBackgroundFrame, completion: { [weak self] completed in
|
||||||
}
|
guard completed, let self, self.currentAnimationId == animationId, let params = self.params else {
|
||||||
self.isAnimating = false
|
return
|
||||||
self.update(
|
}
|
||||||
params: params,
|
self.isAnimating = false
|
||||||
animation: .None,
|
self.update(
|
||||||
synchronously: true,
|
params: params,
|
||||||
animateContents: false,
|
animation: .None,
|
||||||
spoilerExpandRect: nil
|
synchronously: true,
|
||||||
)
|
animateContents: false,
|
||||||
})
|
spoilerExpandRect: nil
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
blockBackgroundView.layer.frame = blockBackgroundFrame
|
blockBackgroundView.layer.frame = blockBackgroundFrame
|
||||||
}
|
}
|
||||||
@ -2431,9 +2515,9 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
} else {
|
} else {
|
||||||
if let contentMaskNode = self.contentMaskNode {
|
if let contentMaskNode = self.contentMaskNode {
|
||||||
self.contentMaskNode = nil
|
self.contentMaskNode = nil
|
||||||
self.renderNode.layer.mask = nil
|
|
||||||
contentMaskNode.layer.removeFromSuperlayer()
|
contentMaskNode.layer.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
self.renderNode.layer.mask = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.item.segment.spoilers.isEmpty {
|
if !params.item.segment.spoilers.isEmpty {
|
||||||
@ -2473,6 +2557,36 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
overlayContentLayer.frame = effectiveContentFrame
|
overlayContentLayer.frame = effectiveContentFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let contentMask {
|
||||||
|
var overlayContentMaskAnimation = animation
|
||||||
|
let overlayContentMaskNode: ASImageNode
|
||||||
|
if let current = self.overlayContentMaskNode {
|
||||||
|
overlayContentMaskNode = current
|
||||||
|
} else {
|
||||||
|
overlayContentMaskNode = ASImageNode()
|
||||||
|
overlayContentMaskNode.isLayerBacked = true
|
||||||
|
overlayContentMaskNode.backgroundColor = .clear
|
||||||
|
self.overlayContentMaskNode = overlayContentMaskNode
|
||||||
|
overlayContentLayer.mask = overlayContentMaskNode.layer
|
||||||
|
|
||||||
|
if let currentContentMask = self.currentContentMask {
|
||||||
|
overlayContentMaskNode.frame = currentContentMask.frame
|
||||||
|
} else {
|
||||||
|
overlayContentMaskAnimation = .None
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayContentMaskNode.image = contentMask.image
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayContentMaskAnimation.animator.updateBackgroundColor(layer: overlayContentMaskNode.layer, color: contentMask.isOpaque ? UIColor.white : UIColor.clear, completion: nil)
|
||||||
|
overlayContentMaskAnimation.animator.updateFrame(layer: overlayContentMaskNode.layer, frame: contentMask.frame, completion: nil)
|
||||||
|
} else {
|
||||||
|
if let _ = self.overlayContentMaskNode {
|
||||||
|
self.overlayContentMaskNode = nil
|
||||||
|
overlayContentLayer.mask = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let spoilerEffectNode = self.spoilerEffectNode {
|
if let spoilerEffectNode = self.spoilerEffectNode {
|
||||||
if spoilerEffectNode.layer.superlayer !== overlayContentLayer {
|
if spoilerEffectNode.layer.superlayer !== overlayContentLayer {
|
||||||
overlayContentLayer.addSublayer(spoilerEffectNode.layer)
|
overlayContentLayer.addSublayer(spoilerEffectNode.layer)
|
||||||
@ -2593,6 +2707,7 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
|
|
||||||
if let spoilerEffectNode = self.spoilerEffectNode {
|
if let spoilerEffectNode = self.spoilerEffectNode {
|
||||||
animation.transition.updateAlpha(layer: spoilerEffectNode.layer, alpha: params.item.displayContentsUnderSpoilers ? 0.0 : 1.0)
|
animation.transition.updateAlpha(layer: spoilerEffectNode.layer, alpha: params.item.displayContentsUnderSpoilers ? 0.0 : 1.0)
|
||||||
|
spoilerEffectNode.update(revealed: params.item.displayContentsUnderSpoilers, animated: animation.isAnimated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent",
|
"//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen",
|
"//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen",
|
||||||
"//submodules/TelegramUI/Components/SliderContextItem",
|
"//submodules/TelegramUI/Components/SliderContextItem",
|
||||||
|
"//submodules/TelegramUI/Components/InteractiveTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -8,13 +8,13 @@ import Postbox
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TextNodeWithEntities
|
import TextNodeWithEntities
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import InvisibleInkDustNode
|
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TextSelectionNode
|
import TextSelectionNode
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import ForwardInfoPanelComponent
|
import ForwardInfoPanelComponent
|
||||||
import PlainButtonComponent
|
import PlainButtonComponent
|
||||||
|
import InteractiveTextComponent
|
||||||
|
|
||||||
final class StoryContentCaptionComponent: Component {
|
final class StoryContentCaptionComponent: Component {
|
||||||
enum Action {
|
enum Action {
|
||||||
@ -152,10 +152,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class ContentItem: UIView {
|
private final class ContentItem: UIView {
|
||||||
var textNode: TextNodeWithEntities?
|
var textNode: InteractiveTextNodeWithEntities?
|
||||||
var spoilerTextNode: TextNodeWithEntities?
|
|
||||||
var linkHighlightingNode: LinkHighlightingNode?
|
var linkHighlightingNode: LinkHighlightingNode?
|
||||||
var dustNode: InvisibleInkDustNode?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -198,6 +196,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
private var ignoreScrolling: Bool = false
|
private var ignoreScrolling: Bool = false
|
||||||
private var ignoreExternalState: Bool = false
|
private var ignoreExternalState: Bool = false
|
||||||
|
|
||||||
|
private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil)
|
||||||
|
private var expandedContentsBlocks: Set<Int> = Set()
|
||||||
private var isExpanded: Bool = false
|
private var isExpanded: Bool = false
|
||||||
|
|
||||||
private var codeHighlight: CachedMessageSyntaxHighlight?
|
private var codeHighlight: CachedMessageSyntaxHighlight?
|
||||||
@ -449,20 +449,17 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||||
let otherContentItem = !self.isExpanded ? self.expandedText : self.collapsedText
|
|
||||||
|
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .ended:
|
case .ended:
|
||||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = contentItem.textNode {
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = contentItem.textNode {
|
||||||
let titleFrame = textNode.textNode.view.bounds
|
let titleFrame = textNode.textNode.view.bounds
|
||||||
if titleFrame.contains(location) {
|
if titleFrame.contains(location) {
|
||||||
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
let textLocalPoint = CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)
|
||||||
|
if let (index, attributes) = textNode.textNode.attributesAtPoint(textLocalPoint) {
|
||||||
let action: Action?
|
let action: Action?
|
||||||
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(contentItem.dustNode?.isRevealed ?? true) {
|
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers.value {
|
||||||
let convertedPoint = recognizer.view?.convert(location, to: contentItem.dustNode?.view) ?? location
|
self.updateDisplayContentsUnderSpoilers(value: true, at: recognizer.view?.convert(location, to: textNode.textNode.view) ?? location)
|
||||||
contentItem.dustNode?.revealAtLocation(convertedPoint)
|
|
||||||
otherContentItem.dustNode?.revealAtLocation(convertedPoint)
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
|
||||||
return
|
return
|
||||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
@ -497,7 +494,17 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
if component.externalState.isSelectingText {
|
if component.externalState.isSelectingText {
|
||||||
self.cancelTextSelection()
|
self.cancelTextSelection()
|
||||||
} else if self.isExpanded {
|
} else if self.isExpanded {
|
||||||
self.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
if let blockIndex = textNode.textNode.collapsibleBlockAtPoint(textLocalPoint) {
|
||||||
|
if self.expandedContentsBlocks.contains(blockIndex) {
|
||||||
|
self.expandedContentsBlocks.remove(blockIndex)
|
||||||
|
} else {
|
||||||
|
self.expandedContentsBlocks.insert(blockIndex)
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
} else {
|
||||||
|
self.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
}
|
}
|
||||||
@ -531,6 +538,14 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateDisplayContentsUnderSpoilers(value: Bool, at location: CGPoint?) {
|
||||||
|
if self.displayContentsUnderSpoilers.value == value {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.displayContentsUnderSpoilers = (value, location)
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
|
||||||
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||||
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||||
|
|
||||||
@ -563,7 +578,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = contentItem.dustNode, !dustNode.isRevealed {
|
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !self.displayContentsUnderSpoilers.value {
|
||||||
} else if let rects = rects {
|
} else if let rects = rects {
|
||||||
let linkHighlightingNode: LinkHighlightingNode
|
let linkHighlightingNode: LinkHighlightingNode
|
||||||
if let current = contentItem.linkHighlightingNode {
|
if let current = contentItem.linkHighlightingNode {
|
||||||
@ -652,46 +667,41 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
cachedMessageSyntaxHighlight: self.codeHighlight
|
cachedMessageSyntaxHighlight: self.codeHighlight
|
||||||
)
|
)
|
||||||
|
|
||||||
let truncationToken = NSMutableAttributedString()
|
let truncationTokenString = component.strings.Story_CaptionShowMore
|
||||||
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white))
|
let customTruncationToken: (UIFont, Bool) -> NSAttributedString? = { baseFont, _ in
|
||||||
truncationToken.append(NSAttributedString(string: component.strings.Story_CaptionShowMore, font: Font.semibold(16.0), textColor: .white))
|
let truncationToken = NSMutableAttributedString()
|
||||||
|
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(baseFont.pointSize), textColor: .white))
|
||||||
|
truncationToken.append(NSAttributedString(string: truncationTokenString, font: Font.semibold(baseFont.pointSize), textColor: .white))
|
||||||
|
return truncationToken
|
||||||
|
}
|
||||||
|
|
||||||
let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments(
|
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
|
||||||
|
let collapsedTextLayout = InteractiveTextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(InteractiveTextNodeLayoutArguments(
|
||||||
attributedString: attributedText,
|
attributedString: attributedText,
|
||||||
maximumNumberOfLines: 3,
|
maximumNumberOfLines: 3,
|
||||||
truncationType: .end,
|
truncationType: .end,
|
||||||
constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0),
|
constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0),
|
||||||
|
insets: textInsets,
|
||||||
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
||||||
textShadowBlur: 4.0,
|
textShadowBlur: 4.0,
|
||||||
displaySpoilers: false,
|
displayContentsUnderSpoilers: self.displayContentsUnderSpoilers.value,
|
||||||
customTruncationToken: truncationToken
|
customTruncationToken: customTruncationToken,
|
||||||
|
expandedBlocks: self.expandedContentsBlocks
|
||||||
))
|
))
|
||||||
let expandedTextLayout = TextNodeWithEntities.asyncLayout(self.expandedText.textNode)(TextNodeLayoutArguments(
|
let expandedTextLayout = InteractiveTextNodeWithEntities.asyncLayout(self.expandedText.textNode)(InteractiveTextNodeLayoutArguments(
|
||||||
attributedString: attributedText,
|
attributedString: attributedText,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
truncationType: .end,
|
truncationType: .end,
|
||||||
constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0),
|
constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0),
|
||||||
|
insets: textInsets,
|
||||||
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
||||||
textShadowBlur: 4.0,
|
textShadowBlur: 4.0,
|
||||||
displaySpoilers: false
|
displayContentsUnderSpoilers: self.displayContentsUnderSpoilers.value,
|
||||||
|
expandedBlocks: self.expandedContentsBlocks
|
||||||
))
|
))
|
||||||
|
|
||||||
let collapsedSpoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
let visibleTextHeight = collapsedTextLayout.0.size.height - textInsets.top - textInsets.bottom
|
||||||
if !collapsedTextLayout.0.spoilers.isEmpty {
|
let textOverflowHeight: CGFloat = expandedTextLayout.0.size.height - textInsets.top - textInsets.bottom - visibleTextHeight
|
||||||
collapsedSpoilerTextLayoutAndApply = TextNodeWithEntities.asyncLayout(self.collapsedText.spoilerTextNode)(TextNodeLayoutArguments(attributedString: attributedText, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0), textShadowColor: UIColor(white: 0.0, alpha: 0.25), textShadowBlur: 4.0, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
|
||||||
} else {
|
|
||||||
collapsedSpoilerTextLayoutAndApply = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let expandedSpoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
|
||||||
if !expandedTextLayout.0.spoilers.isEmpty {
|
|
||||||
expandedSpoilerTextLayoutAndApply = TextNodeWithEntities.asyncLayout(self.expandedText.spoilerTextNode)(TextNodeLayoutArguments(attributedString: attributedText, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: textContainerSize.width, height: 10000.0), textShadowColor: UIColor(white: 0.0, alpha: 0.25), textShadowBlur: 4.0, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
|
||||||
} else {
|
|
||||||
expandedSpoilerTextLayoutAndApply = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let visibleTextHeight = collapsedTextLayout.0.size.height
|
|
||||||
let textOverflowHeight: CGFloat = expandedTextLayout.0.size.height - visibleTextHeight
|
|
||||||
let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
|
let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
|
||||||
|
|
||||||
if let forwardInfo = component.forwardInfo {
|
if let forwardInfo = component.forwardInfo {
|
||||||
@ -789,14 +799,55 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
forwardInfoPanel.view?.removeFromSuperview()
|
forwardInfoPanel.view?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let collapsedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: availableSize.height - visibleTextHeight - verticalInset - textInsets.top), size: collapsedTextLayout.0.size)
|
||||||
|
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: availableSize.height - visibleTextHeight - verticalInset - textInsets.top), size: expandedTextLayout.0.size)
|
||||||
|
|
||||||
|
var spoilerExpandRect: CGRect?
|
||||||
|
if let location = self.displayContentsUnderSpoilers.location {
|
||||||
|
self.displayContentsUnderSpoilers.location = nil
|
||||||
|
|
||||||
|
let mappedLocation = CGPoint(x: location.x, y: location.y)
|
||||||
|
|
||||||
|
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: expandedTextFrame.width, y: 0.0)))
|
||||||
|
maxDistance = max(maxDistance, getDistance(mappedLocation, CGPoint(x: expandedTextFrame.width, y: expandedTextFrame.height)))
|
||||||
|
maxDistance = max(maxDistance, getDistance(mappedLocation, CGPoint(x: 0.0, y: expandedTextFrame.height)))
|
||||||
|
|
||||||
|
let mappedSize = CGSize(width: maxDistance * 2.0, height: maxDistance * 2.0)
|
||||||
|
spoilerExpandRect = mappedSize.centered(around: mappedLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textAnimation: ListViewItemUpdateAnimation
|
||||||
|
if case let .curve(duration, curve) = transition.animation {
|
||||||
|
textAnimation = .System(duration: duration, transition: ControlledTransition(duration: duration, curve: curve.containedViewLayoutTransitionCurve, interactive: false))
|
||||||
|
} else {
|
||||||
|
textAnimation = .None
|
||||||
|
}
|
||||||
|
let textApplyArguments = InteractiveTextNodeWithEntities.Arguments(
|
||||||
|
context: component.context,
|
||||||
|
cache: component.context.animationCache,
|
||||||
|
renderer: component.context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
||||||
|
attemptSynchronous: true,
|
||||||
|
textColor: .white,
|
||||||
|
spoilerEffectColor: .white,
|
||||||
|
applyArguments: InteractiveTextNode.ApplyArguments(
|
||||||
|
animation: textAnimation,
|
||||||
|
spoilerTextColor: .white,
|
||||||
|
spoilerEffectColor: .white,
|
||||||
|
areContentAnimationsEnabled: true,
|
||||||
|
spoilerExpandRect: spoilerExpandRect
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let collapsedTextNode = collapsedTextLayout.1(TextNodeWithEntities.Arguments(
|
let collapsedTextNode = collapsedTextLayout.1(textApplyArguments)
|
||||||
context: component.context,
|
|
||||||
cache: component.context.animationCache,
|
|
||||||
renderer: component.context.animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
|
||||||
attemptSynchronous: true
|
|
||||||
))
|
|
||||||
if self.collapsedText.textNode !== collapsedTextNode {
|
if self.collapsedText.textNode !== collapsedTextNode {
|
||||||
self.collapsedText.textNode?.textNode.view.removeFromSuperview()
|
self.collapsedText.textNode?.textNode.view.removeFromSuperview()
|
||||||
|
|
||||||
@ -810,61 +861,11 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
collapsedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
collapsedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
let collapsedTextFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: collapsedTextLayout.0.size)
|
|
||||||
collapsedTextNode.textNode.frame = collapsedTextFrame
|
collapsedTextNode.textNode.frame = collapsedTextFrame
|
||||||
|
|
||||||
if let (_, collapsedSpoilerTextApply) = collapsedSpoilerTextLayoutAndApply {
|
|
||||||
let collapsedSpoilerTextNode = collapsedSpoilerTextApply(TextNodeWithEntities.Arguments(
|
|
||||||
context: component.context,
|
|
||||||
cache: component.context.animationCache,
|
|
||||||
renderer: component.context.animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
|
||||||
attemptSynchronous: true
|
|
||||||
))
|
|
||||||
if self.collapsedText.spoilerTextNode == nil {
|
|
||||||
collapsedSpoilerTextNode.textNode.alpha = 0.0
|
|
||||||
collapsedSpoilerTextNode.textNode.isUserInteractionEnabled = false
|
|
||||||
collapsedSpoilerTextNode.textNode.contentMode = .topLeft
|
|
||||||
collapsedSpoilerTextNode.textNode.contentsScale = UIScreenScale
|
|
||||||
collapsedSpoilerTextNode.textNode.displaysAsynchronously = false
|
|
||||||
self.collapsedText.insertSubview(collapsedSpoilerTextNode.textNode.view, belowSubview: collapsedTextNode.textNode.view)
|
|
||||||
|
|
||||||
collapsedSpoilerTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
|
||||||
|
|
||||||
self.collapsedText.spoilerTextNode = collapsedSpoilerTextNode
|
|
||||||
}
|
|
||||||
|
|
||||||
self.collapsedText.spoilerTextNode?.textNode.frame = collapsedTextFrame
|
|
||||||
|
|
||||||
let collapsedDustNode: InvisibleInkDustNode
|
|
||||||
if let current = self.collapsedText.dustNode {
|
|
||||||
collapsedDustNode = current
|
|
||||||
} else {
|
|
||||||
collapsedDustNode = InvisibleInkDustNode(textNode: collapsedSpoilerTextNode.textNode, enableAnimations: component.context.sharedContext.energyUsageSettings.fullTranslucency)
|
|
||||||
self.collapsedText.dustNode = collapsedDustNode
|
|
||||||
self.collapsedText.insertSubview(collapsedDustNode.view, aboveSubview: collapsedSpoilerTextNode.textNode.view)
|
|
||||||
}
|
|
||||||
collapsedDustNode.frame = collapsedTextFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 0.0)
|
|
||||||
collapsedDustNode.update(size: collapsedDustNode.frame.size, color: .white, textColor: .white, rects: collapsedTextLayout.0.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: collapsedTextLayout.0.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
|
||||||
} else if let collapsedSpoilerTextNode = self.collapsedText.spoilerTextNode {
|
|
||||||
self.collapsedText.spoilerTextNode = nil
|
|
||||||
collapsedSpoilerTextNode.textNode.removeFromSupernode()
|
|
||||||
|
|
||||||
if let collapsedDustNode = self.collapsedText.dustNode {
|
|
||||||
self.collapsedText.dustNode = nil
|
|
||||||
collapsedDustNode.view.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let expandedTextNode = expandedTextLayout.1(TextNodeWithEntities.Arguments(
|
let expandedTextNode = expandedTextLayout.1(textApplyArguments)
|
||||||
context: component.context,
|
|
||||||
cache: component.context.animationCache,
|
|
||||||
renderer: component.context.animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
|
||||||
attemptSynchronous: true
|
|
||||||
))
|
|
||||||
if self.expandedText.textNode !== expandedTextNode {
|
if self.expandedText.textNode !== expandedTextNode {
|
||||||
self.expandedText.textNode?.textNode.view.removeFromSuperview()
|
self.expandedText.textNode?.textNode.view.removeFromSuperview()
|
||||||
|
|
||||||
@ -876,51 +877,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
expandedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
expandedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: expandedTextLayout.0.size)
|
|
||||||
expandedTextNode.textNode.frame = expandedTextFrame
|
expandedTextNode.textNode.frame = expandedTextFrame
|
||||||
|
|
||||||
if let (_, expandedSpoilerTextApply) = expandedSpoilerTextLayoutAndApply {
|
|
||||||
let expandedSpoilerTextNode = expandedSpoilerTextApply(TextNodeWithEntities.Arguments(
|
|
||||||
context: component.context,
|
|
||||||
cache: component.context.animationCache,
|
|
||||||
renderer: component.context.animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
|
||||||
attemptSynchronous: true
|
|
||||||
))
|
|
||||||
if self.expandedText.spoilerTextNode == nil {
|
|
||||||
expandedSpoilerTextNode.textNode.alpha = 0.0
|
|
||||||
expandedSpoilerTextNode.textNode.isUserInteractionEnabled = false
|
|
||||||
expandedSpoilerTextNode.textNode.contentMode = .topLeft
|
|
||||||
expandedSpoilerTextNode.textNode.contentsScale = UIScreenScale
|
|
||||||
expandedSpoilerTextNode.textNode.displaysAsynchronously = false
|
|
||||||
self.expandedText.insertSubview(expandedSpoilerTextNode.textNode.view, belowSubview: expandedTextNode.textNode.view)
|
|
||||||
|
|
||||||
expandedSpoilerTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
|
||||||
|
|
||||||
self.expandedText.spoilerTextNode = expandedSpoilerTextNode
|
|
||||||
}
|
|
||||||
|
|
||||||
self.expandedText.spoilerTextNode?.textNode.frame = expandedTextFrame
|
|
||||||
|
|
||||||
let expandedDustNode: InvisibleInkDustNode
|
|
||||||
if let current = self.expandedText.dustNode {
|
|
||||||
expandedDustNode = current
|
|
||||||
} else {
|
|
||||||
expandedDustNode = InvisibleInkDustNode(textNode: expandedSpoilerTextNode.textNode, enableAnimations: component.context.sharedContext.energyUsageSettings.fullTranslucency)
|
|
||||||
self.expandedText.dustNode = expandedDustNode
|
|
||||||
self.expandedText.insertSubview(expandedDustNode.view, aboveSubview: expandedSpoilerTextNode.textNode.view)
|
|
||||||
}
|
|
||||||
expandedDustNode.frame = expandedTextFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 0.0)
|
|
||||||
expandedDustNode.update(size: expandedDustNode.frame.size, color: .white, textColor: .white, rects: expandedTextLayout.0.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: expandedTextLayout.0.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
|
||||||
} else if let expandedSpoilerTextNode = self.expandedText.spoilerTextNode {
|
|
||||||
self.expandedText.spoilerTextNode = nil
|
|
||||||
expandedSpoilerTextNode.textNode.removeFromSupernode()
|
|
||||||
|
|
||||||
if let expandedDustNode = self.expandedText.dustNode {
|
|
||||||
self.expandedText.dustNode = nil
|
|
||||||
expandedDustNode.view.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.textSelectionNode == nil, let controller = component.controller(), let textNode = self.expandedText.textNode?.textNode {
|
if self.textSelectionNode == nil, let controller = component.controller(), let textNode = self.expandedText.textNode?.textNode {
|
||||||
@ -956,16 +913,6 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
component.textSelectionAction(text, action)
|
component.textSelectionAction(text, action)
|
||||||
})
|
})
|
||||||
/*textSelectionNode.updateRange = { [weak self] selectionRange in
|
|
||||||
if let strongSelf = self, let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty, let selectionRange = selectionRange {
|
|
||||||
for (spoilerRange, _) in textLayout.spoilers {
|
|
||||||
if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 {
|
|
||||||
dustNode.update(revealed: true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
textSelectionNode.enableLookup = true
|
textSelectionNode.enableLookup = true
|
||||||
self.textSelectionNode = textSelectionNode
|
self.textSelectionNode = textSelectionNode
|
||||||
self.scrollView.addSubview(textSelectionNode.view)
|
self.scrollView.addSubview(textSelectionNode.view)
|
||||||
@ -985,7 +932,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
|
|
||||||
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
||||||
let action: Action?
|
let action: Action?
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(contentItem.dustNode?.isRevealed ?? true) {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers.value {
|
||||||
return false
|
return false
|
||||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
@ -1014,15 +961,9 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
//let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
|
||||||
//textSelectionNode.view.addGestureRecognizer(tapRecognizer)
|
|
||||||
|
|
||||||
let _ = textSelectionNode.view
|
let _ = textSelectionNode.view
|
||||||
|
|
||||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||||
/*if let selectionRecognizer = textSelectionNode.recognizer {
|
|
||||||
recognizer.require(toFail: selectionRecognizer)
|
|
||||||
}*/
|
|
||||||
recognizer.tapActionAtPoint = { point in
|
recognizer.tapActionAtPoint = { point in
|
||||||
return .waitForSingleTap
|
return .waitForSingleTap
|
||||||
}
|
}
|
||||||
@ -1050,6 +991,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.ignoreScrolling = true
|
self.ignoreScrolling = true
|
||||||
|
let previousBounds = self.scrollView.bounds
|
||||||
|
|
||||||
if self.scrollView.contentSize != scrollContentSize {
|
if self.scrollView.contentSize != scrollContentSize {
|
||||||
self.scrollView.contentSize = scrollContentSize
|
self.scrollView.contentSize = scrollContentSize
|
||||||
@ -1057,27 +999,13 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||||
transition.setFrame(view: self.scrollViewContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
transition.setFrame(view: self.scrollViewContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||||
|
|
||||||
/*if self.shadowGradientLayer.colors == nil {
|
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
||||||
var locations: [NSNumber] = []
|
let bounds = self.scrollView.bounds
|
||||||
var colors: [CGColor] = []
|
if bounds.maxY != previousBounds.maxY {
|
||||||
let numStops = 10
|
let offsetY = previousBounds.maxY - bounds.maxY
|
||||||
let baseAlpha: CGFloat = 0.6
|
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||||
for i in 0 ..< numStops {
|
|
||||||
let step = 1.0 - CGFloat(i) / CGFloat(numStops - 1)
|
|
||||||
locations.append((1.0 - step) as NSNumber)
|
|
||||||
let alphaStep: CGFloat = pow(step, 1.0)
|
|
||||||
colors.append(UIColor.black.withAlphaComponent(alphaStep * baseAlpha).cgColor)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.shadowGradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
|
|
||||||
self.shadowGradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
|
|
||||||
|
|
||||||
self.shadowGradientLayer.locations = locations
|
|
||||||
self.shadowGradientLayer.colors = colors
|
|
||||||
self.shadowGradientLayer.type = .axial
|
|
||||||
|
|
||||||
self.shadowPlainLayer.backgroundColor = UIColor(white: 0.0, alpha: baseAlpha).cgColor
|
|
||||||
}*/
|
|
||||||
|
|
||||||
self.ignoreScrolling = false
|
self.ignoreScrolling = false
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
@ -1100,33 +1028,6 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
isExpandedTransition.setAlpha(view: self.collapsedText, alpha: self.isExpanded ? 0.0 : 1.0)
|
isExpandedTransition.setAlpha(view: self.collapsedText, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||||
isExpandedTransition.setAlpha(view: self.expandedText, alpha: !self.isExpanded ? 0.0 : 1.0)
|
isExpandedTransition.setAlpha(view: self.expandedText, alpha: !self.isExpanded ? 0.0 : 1.0)
|
||||||
|
|
||||||
/*if let spoilerTextNode = self.collapsedText.spoilerTextNode {
|
|
||||||
var spoilerAlpha = self.isExpanded ? 0.0 : 1.0
|
|
||||||
if let dustNode = self.collapsedText.dustNode, dustNode.isRevealed {
|
|
||||||
} else {
|
|
||||||
spoilerAlpha = 0.0
|
|
||||||
}
|
|
||||||
isExpandedTransition.setAlpha(view: spoilerTextNode.textNode.view, alpha: spoilerAlpha)
|
|
||||||
}
|
|
||||||
if let dustNode = self.collapsedText.dustNode {
|
|
||||||
isExpandedTransition.setAlpha(view: dustNode.view, alpha: self.isExpanded ? 0.0 : 1.0)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*if let textNode = self.expandedText.textNode {
|
|
||||||
isExpandedTransition.setAlpha(view: textNode.textNode.view, alpha: !self.isExpanded ? 0.0 : 1.0)
|
|
||||||
}
|
|
||||||
if let spoilerTextNode = self.expandedText.spoilerTextNode {
|
|
||||||
var spoilerAlpha = !self.isExpanded ? 0.0 : 1.0
|
|
||||||
if let dustNode = self.expandedText.dustNode, dustNode.isRevealed {
|
|
||||||
} else {
|
|
||||||
spoilerAlpha = 0.0
|
|
||||||
}
|
|
||||||
isExpandedTransition.setAlpha(view: spoilerTextNode.textNode.view, alpha: spoilerAlpha)
|
|
||||||
}
|
|
||||||
if let dustNode = self.expandedText.dustNode {
|
|
||||||
isExpandedTransition.setAlpha(view: dustNode.view, alpha: !self.isExpanded ? 0.0 : 1.0)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
isExpandedTransition.setAlpha(view: self.shadowGradientView, alpha: self.isExpanded ? 0.0 : 1.0)
|
isExpandedTransition.setAlpha(view: self.shadowGradientView, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||||
|
|
||||||
isExpandedTransition.setAlpha(view: self.scrollBottomMaskView, alpha: self.isExpanded ? 1.0 : 0.0)
|
isExpandedTransition.setAlpha(view: self.scrollBottomMaskView, alpha: self.isExpanded ? 1.0 : 0.0)
|
||||||
|
@ -300,6 +300,59 @@ public final class TextFieldComponent: Component {
|
|||||||
NSAttributedString.Key.font: Font.regular(17.0),
|
NSAttributedString.Key.font: Font.regular(17.0),
|
||||||
NSAttributedString.Key.foregroundColor: UIColor.white
|
NSAttributedString.Key.foregroundColor: UIColor.white
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.textView.toggleQuoteCollapse = { [weak self] range in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateInputState { current in
|
||||||
|
let result = NSMutableAttributedString(attributedString: current.inputText)
|
||||||
|
var selectionRange = current.selectionRange
|
||||||
|
|
||||||
|
if let _ = result.attribute(ChatTextInputAttributes.block, at: range.lowerBound, effectiveRange: nil) as? ChatTextInputTextQuoteAttribute {
|
||||||
|
let blockString = NSMutableAttributedString(attributedString: result.attributedSubstring(from: range))
|
||||||
|
blockString.removeAttribute(ChatTextInputAttributes.block, range: NSRange(location: 0, length: blockString.length))
|
||||||
|
|
||||||
|
result.replaceCharacters(in: range, with: "")
|
||||||
|
result.insert(NSAttributedString(string: " ", attributes: [
|
||||||
|
ChatTextInputAttributes.collapsedBlock: blockString
|
||||||
|
]), at: range.lowerBound)
|
||||||
|
|
||||||
|
if selectionRange.lowerBound >= range.lowerBound && selectionRange.upperBound < range.upperBound {
|
||||||
|
selectionRange = range.lowerBound ..< range.lowerBound
|
||||||
|
} else if selectionRange.lowerBound >= range.upperBound {
|
||||||
|
let deltaLength = 1 - range.length
|
||||||
|
selectionRange = (selectionRange.lowerBound + deltaLength) ..< (selectionRange.lowerBound + deltaLength)
|
||||||
|
}
|
||||||
|
} else if let current = result.attribute(ChatTextInputAttributes.collapsedBlock, at: range.lowerBound, effectiveRange: nil) as? NSAttributedString {
|
||||||
|
result.replaceCharacters(in: range, with: "")
|
||||||
|
|
||||||
|
let updatedBlockString = NSMutableAttributedString(attributedString: current)
|
||||||
|
updatedBlockString.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false), range: NSRange(location: 0, length: updatedBlockString.length))
|
||||||
|
|
||||||
|
result.insert(updatedBlockString, at: range.lowerBound)
|
||||||
|
|
||||||
|
if selectionRange.lowerBound >= range.upperBound {
|
||||||
|
let deltaLength = updatedBlockString.length - 1
|
||||||
|
selectionRange = (selectionRange.lowerBound + deltaLength) ..< (selectionRange.lowerBound + deltaLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stateResult = stateAttributedStringForText(result)
|
||||||
|
if selectionRange.lowerBound < 0 {
|
||||||
|
selectionRange = 0 ..< selectionRange.upperBound
|
||||||
|
}
|
||||||
|
if selectionRange.upperBound > stateResult.length {
|
||||||
|
selectionRange = selectionRange.lowerBound ..< stateResult.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return InputState(
|
||||||
|
inputText: stateResult,
|
||||||
|
selectionRange: selectionRange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -900,7 +953,7 @@ public final class TextFieldComponent: Component {
|
|||||||
|
|
||||||
public func getAttributedText() -> NSAttributedString {
|
public func getAttributedText() -> NSAttributedString {
|
||||||
Keyboard.applyAutocorrection(textView: self.textView)
|
Keyboard.applyAutocorrection(textView: self.textView)
|
||||||
return self.inputState.inputText
|
return expandedInputStateAttributedString(self.inputState.inputText)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setAttributedText(_ string: NSAttributedString, updateState: Bool) {
|
public func setAttributedText(_ string: NSAttributedString, updateState: Bool) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user