mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories
This commit is contained in:
parent
e8b3615be6
commit
6ec1f4c680
@ -149,6 +149,7 @@ public final class TextNodeLayoutArguments {
|
||||
public let textStroke: (UIColor, CGFloat)?
|
||||
public let displaySpoilers: Bool
|
||||
public let displayEmbeddedItemsUnderSpoilers: Bool
|
||||
public let customTruncationToken: NSAttributedString?
|
||||
|
||||
public init(
|
||||
attributedString: NSAttributedString?,
|
||||
@ -167,7 +168,8 @@ public final class TextNodeLayoutArguments {
|
||||
textShadowBlur: CGFloat? = nil,
|
||||
textStroke: (UIColor, CGFloat)? = nil,
|
||||
displaySpoilers: Bool = false,
|
||||
displayEmbeddedItemsUnderSpoilers: Bool = false
|
||||
displayEmbeddedItemsUnderSpoilers: Bool = false,
|
||||
customTruncationToken: NSAttributedString? = nil
|
||||
) {
|
||||
self.attributedString = attributedString
|
||||
self.backgroundColor = backgroundColor
|
||||
@ -186,6 +188,7 @@ public final class TextNodeLayoutArguments {
|
||||
self.textStroke = textStroke
|
||||
self.displaySpoilers = displaySpoilers
|
||||
self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers
|
||||
self.customTruncationToken = customTruncationToken
|
||||
}
|
||||
|
||||
public func withAttributedString(_ attributedString: NSAttributedString?) -> TextNodeLayoutArguments {
|
||||
@ -206,7 +209,8 @@ public final class TextNodeLayoutArguments {
|
||||
textShadowBlur: self.textShadowBlur,
|
||||
textStroke: self.textStroke,
|
||||
displaySpoilers: self.displaySpoilers,
|
||||
displayEmbeddedItemsUnderSpoilers: self.displayEmbeddedItemsUnderSpoilers
|
||||
displayEmbeddedItemsUnderSpoilers: self.displayEmbeddedItemsUnderSpoilers,
|
||||
customTruncationToken: self.customTruncationToken
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -998,7 +1002,7 @@ open class TextNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
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)?, displaySpoilers: Bool, displayEmbeddedItemsUnderSpoilers: Bool) -> TextNodeLayout {
|
||||
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)?, displaySpoilers: Bool, displayEmbeddedItemsUnderSpoilers: Bool, customTruncationToken: NSAttributedString?) -> TextNodeLayout {
|
||||
if let attributedString = attributedString {
|
||||
let stringLength = attributedString.length
|
||||
|
||||
@ -1168,7 +1172,17 @@ open class TextNode: ASDisplayNode {
|
||||
layoutSize.height += fontLineSpacing
|
||||
}
|
||||
|
||||
let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex)
|
||||
var didClipLinebreak = false
|
||||
var lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex)
|
||||
let nsString = (attributedString.string as NSString)
|
||||
for i in lineRange.location ..< (lineRange.location + lineRange.length) {
|
||||
if nsString.character(at: i) == 0x0a {
|
||||
lineRange.length = max(0, i - lineRange.location)
|
||||
didClipLinebreak = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var brokenLineRange = CFRange(location: lastLineCharacterIndex, length: lineCharacterCount)
|
||||
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||
@ -1186,16 +1200,44 @@ open class TextNode: ASDisplayNode {
|
||||
lineConstrainedSize.width -= bottomCutoutSize.width
|
||||
}
|
||||
|
||||
if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(lineConstrainedSize.width) {
|
||||
coreTextLine = originalLine
|
||||
let truncatedTokenString: NSAttributedString
|
||||
if let customTruncationToken {
|
||||
truncatedTokenString = customTruncationToken
|
||||
} else {
|
||||
var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:]
|
||||
truncationTokenAttributes[NSAttributedString.Key.font] = font
|
||||
truncationTokenAttributes[NSAttributedString.Key(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber
|
||||
let tokenString = "\u{2026}"
|
||||
let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
||||
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
||||
|
||||
|
||||
truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
||||
}
|
||||
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
||||
|
||||
if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(lineConstrainedSize.width) {
|
||||
if didClipLinebreak {
|
||||
let mergedLine = NSMutableAttributedString()
|
||||
mergedLine.append(attributedString.attributedSubstring(from: NSRange(location: lineRange.location, length: lineRange.length)))
|
||||
mergedLine.append(truncatedTokenString)
|
||||
|
||||
coreTextLine = CTLineCreateWithAttributedString(mergedLine)
|
||||
|
||||
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
||||
for run in runs {
|
||||
let runAttributes: NSDictionary = CTRunGetAttributes(run)
|
||||
if let _ = runAttributes["CTForegroundColorFromContext"] {
|
||||
brokenLineRange.length = CTRunGetStringRange(run).location - brokenLineRange.location
|
||||
break
|
||||
}
|
||||
}
|
||||
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||
}
|
||||
|
||||
truncated = true
|
||||
} else {
|
||||
coreTextLine = originalLine
|
||||
}
|
||||
} else {
|
||||
coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(lineConstrainedSize.width), truncationType, truncationToken) ?? truncationToken
|
||||
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
||||
for run in runs {
|
||||
@ -1647,11 +1689,11 @@ open class TextNode: ASDisplayNode {
|
||||
if stringMatch {
|
||||
layout = existingLayout
|
||||
} else {
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers, customTruncationToken: arguments.customTruncationToken)
|
||||
updated = true
|
||||
}
|
||||
} else {
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers, customTruncationToken: arguments.customTruncationToken)
|
||||
updated = true
|
||||
}
|
||||
|
||||
@ -2292,11 +2334,11 @@ open class TextView: UIView {
|
||||
if stringMatch {
|
||||
layout = existingLayout
|
||||
} else {
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers, customTruncationToken: arguments.customTruncationToken)
|
||||
updated = true
|
||||
}
|
||||
} else {
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers)
|
||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers, displayEmbeddedItemsUnderSpoilers: arguments.displayEmbeddedItemsUnderSpoilers, customTruncationToken: arguments.customTruncationToken)
|
||||
updated = true
|
||||
}
|
||||
|
||||
|
@ -1009,11 +1009,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
|
||||
let story = item.story
|
||||
var foundItem: SparseItemGridDisplayItem?
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
foundItem = item
|
||||
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
@ -1026,6 +1028,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
sourceCornerRadius: 0.0,
|
||||
sourceIsAvatar: false
|
||||
)
|
||||
|
||||
if let blurLayer = foundItem?.blurLayer {
|
||||
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||
transition.setAlpha(layer: blurLayer, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
let storyContainerScreen = StoryContainerScreen(
|
||||
@ -1037,16 +1044,23 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
return nil
|
||||
}
|
||||
|
||||
var foundItem: SparseItemGridDisplayItem?
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
foundItem = item
|
||||
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
if let blurLayer = foundItem?.blurLayer {
|
||||
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||
transition.setAlpha(layer: blurLayer, alpha: 1.0)
|
||||
}
|
||||
|
||||
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: self.view,
|
||||
@ -1862,11 +1876,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
private func updateHiddenItems() {
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer, let item = itemLayer.item else {
|
||||
self.itemGrid.forEachVisibleItem { itemValue in
|
||||
guard let itemLayer = itemValue.layer as? ItemLayer, let item = itemLayer.item else {
|
||||
return
|
||||
}
|
||||
itemLayer.isHidden = self.itemInteraction.hiddenMedia.contains(item.story.id)
|
||||
let itemHidden = self.itemInteraction.hiddenMedia.contains(item.story.id)
|
||||
itemLayer.isHidden = itemHidden
|
||||
|
||||
if let blurLayer = itemValue.blurLayer {
|
||||
let transition = Transition.immediate
|
||||
if itemHidden {
|
||||
transition.setAlpha(layer: blurLayer, alpha: 0.0)
|
||||
} else {
|
||||
transition.setAlpha(layer: blurLayer, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1584,7 +1584,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
||||
private var didAnimateIn: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private let focusedItemPromise = Promise<StoryId?>(nil)
|
||||
private let focusedItemPromise = Promise<StoryId?>()
|
||||
public var focusedItem: Signal<StoryId?, NoError> {
|
||||
return self.focusedItemPromise.get()
|
||||
}
|
||||
|
@ -34,8 +34,14 @@ public final class StoryContentItem: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProgressMode {
|
||||
case play
|
||||
case pause
|
||||
case blurred
|
||||
}
|
||||
|
||||
open class View: UIView {
|
||||
open func setIsProgressPaused(_ isProgressPaused: Bool) {
|
||||
open func setProgressMode(_ progressMode: ProgressMode) {
|
||||
}
|
||||
|
||||
open func rewind() {
|
||||
|
@ -99,23 +99,36 @@ final class StoryContentCaptionComponent: Component {
|
||||
self.verticalInset = verticalInset
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentItem {
|
||||
var textNode: TextNodeWithEntities?
|
||||
var spoilerTextNode: TextNodeWithEntities?
|
||||
var linkHighlightingNode: LinkHighlightingNode?
|
||||
var dustNode: InvisibleInkDustNode?
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func update() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let scrollViewContainer: UIView
|
||||
private let scrollView: UIScrollView
|
||||
|
||||
private let collapsedText: ContentItem
|
||||
private let expandedText: ContentItem
|
||||
|
||||
private let scrollMaskContainer: UIView
|
||||
private let scrollFullMaskView: UIView
|
||||
private let scrollCenterMaskView: UIView
|
||||
private let scrollBottomMaskView: UIImageView
|
||||
private let scrollTopMaskView: UIImageView
|
||||
|
||||
private let shadowGradientLayer: SimpleGradientLayer
|
||||
private let shadowPlainLayer: SimpleLayer
|
||||
|
||||
private var textNode: TextNodeWithEntities?
|
||||
private var spoilerTextNode: TextNodeWithEntities?
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
private var dustNode: InvisibleInkDustNode?
|
||||
|
||||
private var component: StoryContentCaptionComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
@ -125,6 +138,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
private var ignoreScrolling: Bool = false
|
||||
private var ignoreExternalState: Bool = false
|
||||
|
||||
private var isExpanded: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shadowGradientLayer = SimpleGradientLayer()
|
||||
self.shadowPlainLayer = SimpleLayer()
|
||||
@ -154,6 +169,15 @@ final class StoryContentCaptionComponent: Component {
|
||||
UIColor(white: 1.0, alpha: 0.0)
|
||||
], locations: [0.0, 1.0]))
|
||||
self.scrollMaskContainer.addSubview(self.scrollBottomMaskView)
|
||||
|
||||
self.scrollTopMaskView = UIImageView(image: generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [
|
||||
UIColor(white: 1.0, alpha: 0.0),
|
||||
UIColor(white: 1.0, alpha: 1.0)
|
||||
], locations: [0.0, 1.0]))
|
||||
self.scrollMaskContainer.addSubview(self.scrollTopMaskView)
|
||||
|
||||
self.collapsedText = ContentItem()
|
||||
self.expandedText = ContentItem()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -178,7 +202,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if let textView = self.textNode?.textNode.view {
|
||||
|
||||
if let textView = self.collapsedText.textNode?.textNode.view {
|
||||
let textLocalPoint = self.convert(point, to: textView)
|
||||
if textLocalPoint.y >= -7.0 {
|
||||
return textView
|
||||
@ -190,7 +215,11 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
if self.isExpanded {
|
||||
self.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
} else {
|
||||
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,6 +267,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
let isExpanded = expandFraction > 0.0
|
||||
|
||||
self.isExpanded = isExpanded
|
||||
|
||||
if component.externalState.isExpanded != isExpanded {
|
||||
component.externalState.isExpanded = isExpanded
|
||||
|
||||
@ -248,16 +279,18 @@ final class StoryContentCaptionComponent: Component {
|
||||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = self.textNode {
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = contentItem.textNode {
|
||||
let titleFrame = textNode.textNode.view.bounds
|
||||
if titleFrame.contains(location) {
|
||||
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
||||
let action: Action?
|
||||
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
|
||||
let convertedPoint = recognizer.view?.convert(location, to: self.dustNode?.view) ?? location
|
||||
self.dustNode?.revealAtLocation(convertedPoint)
|
||||
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(contentItem.dustNode?.isRevealed ?? true) {
|
||||
let convertedPoint = recognizer.view?.convert(location, to: contentItem.dustNode?.view) ?? location
|
||||
contentItem.dustNode?.revealAtLocation(convertedPoint)
|
||||
return
|
||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
@ -278,19 +311,24 @@ final class StoryContentCaptionComponent: Component {
|
||||
} else {
|
||||
action = nil
|
||||
}
|
||||
guard let action else {
|
||||
return
|
||||
if let action {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
component.action(action)
|
||||
case .longTap:
|
||||
component.longTapAction(action)
|
||||
default:
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if case .tap = gesture {
|
||||
if self.isExpanded {
|
||||
self.collapse(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
} else {
|
||||
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
}
|
||||
}
|
||||
switch gesture {
|
||||
case .tap:
|
||||
component.action(action)
|
||||
case .longTap:
|
||||
component.longTapAction(action)
|
||||
default:
|
||||
return
|
||||
}
|
||||
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -300,7 +338,9 @@ final class StoryContentCaptionComponent: Component {
|
||||
}
|
||||
|
||||
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
guard let textNode = self.textNode else {
|
||||
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||
|
||||
guard let textNode = contentItem.textNode else {
|
||||
return
|
||||
}
|
||||
var rects: [CGRect]?
|
||||
@ -329,20 +369,20 @@ final class StoryContentCaptionComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = self.dustNode, !dustNode.isRevealed {
|
||||
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = contentItem.dustNode, !dustNode.isRevealed {
|
||||
} else if let rects = rects {
|
||||
let linkHighlightingNode: LinkHighlightingNode
|
||||
if let current = self.linkHighlightingNode {
|
||||
if let current = contentItem.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
} else {
|
||||
linkHighlightingNode = LinkHighlightingNode(color: UIColor(white: 1.0, alpha: 0.5))
|
||||
self.linkHighlightingNode = linkHighlightingNode
|
||||
contentItem.linkHighlightingNode = linkHighlightingNode
|
||||
self.scrollView.insertSubview(linkHighlightingNode.view, belowSubview: textNode.textNode.view)
|
||||
}
|
||||
linkHighlightingNode.frame = textNode.textNode.view.frame
|
||||
linkHighlightingNode.updateRects(rects)
|
||||
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||
self.linkHighlightingNode = nil
|
||||
} else if let linkHighlightingNode = contentItem.linkHighlightingNode {
|
||||
contentItem.linkHighlightingNode = nil
|
||||
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||
linkHighlightingNode?.removeFromSupernode()
|
||||
})
|
||||
@ -375,8 +415,21 @@ final class StoryContentCaptionComponent: Component {
|
||||
entityFiles: component.entityFiles
|
||||
)
|
||||
|
||||
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
let textLayout = makeLayout(TextNodeLayoutArguments(
|
||||
let truncationToken = NSMutableAttributedString()
|
||||
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white))
|
||||
truncationToken.append(NSAttributedString(string: "Show more", font: Font.semibold(16.0), textColor: .white))
|
||||
|
||||
//TODO:localize
|
||||
let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments(
|
||||
attributedString: attributedText,
|
||||
maximumNumberOfLines: 3,
|
||||
truncationType: .end,
|
||||
constrainedSize: textContainerSize,
|
||||
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
||||
textShadowBlur: 4.0,
|
||||
customTruncationToken: truncationToken
|
||||
))
|
||||
let expandedTextLayout = TextNodeWithEntities.asyncLayout(self.expandedText.textNode)(TextNodeLayoutArguments(
|
||||
attributedString: attributedText,
|
||||
maximumNumberOfLines: 0,
|
||||
truncationType: .end,
|
||||
@ -385,92 +438,177 @@ final class StoryContentCaptionComponent: Component {
|
||||
textShadowBlur: 4.0
|
||||
))
|
||||
|
||||
let makeSpoilerLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
|
||||
let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||
if !textLayout.0.spoilers.isEmpty {
|
||||
spoilerTextLayoutAndApply = makeSpoilerLayout(TextNodeLayoutArguments(attributedString: attributedText, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textContainerSize, textShadowColor: UIColor(white: 0.0, alpha: 0.25), textShadowBlur: 4.0, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
||||
let collapsedSpoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||
if !collapsedTextLayout.0.spoilers.isEmpty {
|
||||
collapsedSpoilerTextLayoutAndApply = TextNodeWithEntities.asyncLayout(self.collapsedText.spoilerTextNode)(TextNodeLayoutArguments(attributedString: attributedText, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textContainerSize, textShadowColor: UIColor(white: 0.0, alpha: 0.25), textShadowBlur: 4.0, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
|
||||
} else {
|
||||
spoilerTextLayoutAndApply = nil
|
||||
collapsedSpoilerTextLayoutAndApply = nil
|
||||
}
|
||||
|
||||
let maxHeight: CGFloat = 50.0
|
||||
let visibleTextHeight = min(maxHeight, textLayout.0.size.height)
|
||||
let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight
|
||||
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: textContainerSize, 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 textNode = textLayout.1(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.textNode !== textNode {
|
||||
self.textNode?.textNode.view.removeFromSuperview()
|
||||
|
||||
self.textNode = textNode
|
||||
if textNode.textNode.view.superview == nil {
|
||||
self.scrollView.addSubview(textNode.textNode.view)
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { point in
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateTouchesAtPoint(point)
|
||||
}
|
||||
textNode.textNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
textNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textLayout.0.size)
|
||||
textNode.textNode.frame = textFrame
|
||||
|
||||
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
||||
let spoilerTextNode = spoilerTextApply(TextNodeWithEntities.Arguments(
|
||||
do {
|
||||
let collapsedTextNode = collapsedTextLayout.1(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.spoilerTextNode == nil {
|
||||
spoilerTextNode.textNode.alpha = 0.0
|
||||
spoilerTextNode.textNode.isUserInteractionEnabled = false
|
||||
spoilerTextNode.textNode.contentMode = .topLeft
|
||||
spoilerTextNode.textNode.contentsScale = UIScreenScale
|
||||
spoilerTextNode.textNode.displaysAsynchronously = false
|
||||
self.scrollView.insertSubview(spoilerTextNode.textNode.view, belowSubview: textNode.textNode.view)
|
||||
if self.collapsedText.textNode !== collapsedTextNode {
|
||||
self.collapsedText.textNode?.textNode.view.removeFromSuperview()
|
||||
|
||||
spoilerTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||
self.collapsedText.textNode = collapsedTextNode
|
||||
if collapsedTextNode.textNode.view.superview == nil {
|
||||
self.scrollView.addSubview(collapsedTextNode.textNode.view)
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { point in
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateTouchesAtPoint(point)
|
||||
}
|
||||
collapsedTextNode.textNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
self.spoilerTextNode = spoilerTextNode
|
||||
collapsedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||
}
|
||||
|
||||
self.spoilerTextNode?.textNode.frame = textFrame
|
||||
let collapsedTextFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: collapsedTextLayout.0.size)
|
||||
collapsedTextNode.textNode.frame = collapsedTextFrame
|
||||
|
||||
let dustNode: InvisibleInkDustNode
|
||||
if let current = self.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: component.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
self.dustNode = dustNode
|
||||
self.scrollView.insertSubview(dustNode.view, aboveSubview: spoilerTextNode.textNode.view)
|
||||
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.scrollView.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.scrollView.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 {
|
||||
let expandedTextNode = expandedTextLayout.1(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.textNode !== expandedTextNode {
|
||||
self.expandedText.textNode?.textNode.view.removeFromSuperview()
|
||||
|
||||
self.expandedText.textNode = expandedTextNode
|
||||
if expandedTextNode.textNode.view.superview == nil {
|
||||
self.scrollView.addSubview(expandedTextNode.textNode.view)
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { point in
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateTouchesAtPoint(point)
|
||||
}
|
||||
expandedTextNode.textNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
expandedTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
|
||||
}
|
||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 0.0)
|
||||
dustNode.update(size: dustNode.frame.size, color: .white, textColor: .white, rects: textLayout.0.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.0.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||
} else if let spoilerTextNode = self.spoilerTextNode {
|
||||
self.spoilerTextNode = nil
|
||||
spoilerTextNode.textNode.removeFromSupernode()
|
||||
|
||||
if let dustNode = self.dustNode {
|
||||
self.dustNode = nil
|
||||
dustNode.removeFromSupernode()
|
||||
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: expandedTextLayout.0.size)
|
||||
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.scrollView.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.scrollView.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,12 +653,41 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
let gradientEdgeHeight: CGFloat = 18.0
|
||||
|
||||
transition.setFrame(view: self.scrollFullMaskView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
transition.setFrame(view: self.scrollCenterMaskView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: availableSize.height - gradientEdgeHeight)))
|
||||
transition.setFrame(view: self.scrollFullMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: gradientEdgeHeight), size: CGSize(width: availableSize.width, height: availableSize.height - gradientEdgeHeight)))
|
||||
transition.setFrame(view: self.scrollCenterMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: gradientEdgeHeight), size: CGSize(width: availableSize.width, height: availableSize.height - gradientEdgeHeight * 2.0)))
|
||||
transition.setFrame(view: self.scrollBottomMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - gradientEdgeHeight), size: CGSize(width: availableSize.width, height: gradientEdgeHeight)))
|
||||
transition.setFrame(view: self.scrollTopMaskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: gradientEdgeHeight)))
|
||||
|
||||
self.ignoreExternalState = false
|
||||
|
||||
var isExpandedTransition = transition
|
||||
if transition.animation.isImmediate, let hint = transition.userData(TransitionHint.self), case .isExpandedUpdated = hint.kind {
|
||||
isExpandedTransition = transition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
|
||||
if let textNode = self.collapsedText.textNode {
|
||||
isExpandedTransition.setAlpha(view: textNode.textNode.view, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||
}
|
||||
if let spoilerTextNode = self.collapsedText.spoilerTextNode {
|
||||
isExpandedTransition.setAlpha(view: spoilerTextNode.textNode.view, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||
}
|
||||
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 {
|
||||
isExpandedTransition.setAlpha(view: spoilerTextNode.textNode.view, alpha: !self.isExpanded ? 0.0 : 1.0)
|
||||
}
|
||||
if let dustNode = self.expandedText.dustNode {
|
||||
isExpandedTransition.setAlpha(view: dustNode.view, alpha: !self.isExpanded ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
isExpandedTransition.setAlpha(layer: self.shadowPlainLayer, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||
isExpandedTransition.setAlpha(layer: self.shadowGradientLayer, alpha: self.isExpanded ? 0.0 : 1.0)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ final class StoryItemContentComponent: Component {
|
||||
private var unsupportedText: ComponentView<Empty>?
|
||||
private var unsupportedButton: ComponentView<Empty>?
|
||||
|
||||
private var isProgressPaused: Bool = true
|
||||
private var progressMode: StoryContentItem.ProgressMode = .pause
|
||||
private var currentProgressTimer: SwiftSignalKit.Timer?
|
||||
private var currentProgressTimerValue: Double = 0.0
|
||||
private var videoProgressDisposable: Disposable?
|
||||
@ -96,7 +96,7 @@ final class StoryItemContentComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateIsProgressPaused(update: true)
|
||||
self.updateProgressMode(update: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ final class StoryItemContentComponent: Component {
|
||||
if self.videoNode != nil {
|
||||
return
|
||||
}
|
||||
if self.isProgressPaused {
|
||||
if case .pause = self.progressMode {
|
||||
return
|
||||
}
|
||||
|
||||
@ -169,7 +169,17 @@ final class StoryItemContentComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.presentationProgressUpdated(1.0, false, true)
|
||||
|
||||
if self.progressMode == .blurred {
|
||||
self.rewind()
|
||||
if let videoNode = self.videoNode {
|
||||
if self.contentLoaded {
|
||||
videoNode.play()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.environment?.presentationProgressUpdated(1.0, false, true)
|
||||
}
|
||||
}
|
||||
videoNode.ownsContentNodeUpdated = { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
@ -206,10 +216,10 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
override func setIsProgressPaused(_ isProgressPaused: Bool) {
|
||||
if self.isProgressPaused != isProgressPaused {
|
||||
self.isProgressPaused = isProgressPaused
|
||||
self.updateIsProgressPaused(update: true)
|
||||
override func setProgressMode(_ progressMode: StoryContentItem.ProgressMode) {
|
||||
if self.progressMode != progressMode {
|
||||
self.progressMode = progressMode
|
||||
self.updateProgressMode(update: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,9 +249,9 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateIsProgressPaused(update: Bool) {
|
||||
private func updateProgressMode(update: Bool) {
|
||||
if let videoNode = self.videoNode {
|
||||
var canPlay = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
|
||||
var canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
|
||||
if let component = self.component {
|
||||
if component.item.isPending {
|
||||
canPlay = false
|
||||
@ -261,7 +271,7 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateProgressTimer() {
|
||||
var needsTimer = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
|
||||
var needsTimer = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy
|
||||
if let component = self.component {
|
||||
if component.item.isPending {
|
||||
needsTimer = false
|
||||
@ -274,7 +284,7 @@ final class StoryItemContentComponent: Component {
|
||||
timeout: 1.0 / 60.0,
|
||||
repeat: true,
|
||||
completion: { [weak self] in
|
||||
guard let self, !self.isProgressPaused, self.contentLoaded, self.hierarchyTrackingLayer.isInHierarchy else {
|
||||
guard let self, self.progressMode != .pause, self.contentLoaded, self.hierarchyTrackingLayer.isInHierarchy else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -288,6 +298,10 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if self.progressMode != .play {
|
||||
return
|
||||
}
|
||||
|
||||
#if DEBUG && true
|
||||
let currentProgressTimerLimit: Double = 10.0
|
||||
#else
|
||||
@ -626,7 +640,7 @@ final class StoryItemContentComponent: Component {
|
||||
self.backgroundColor = UIColor(rgb: 0x181818)
|
||||
}
|
||||
|
||||
self.updateIsProgressPaused(update: false)
|
||||
self.updateProgressMode(update: false)
|
||||
|
||||
if reloadMedia && synchronousLoad {
|
||||
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
||||
|
@ -877,46 +877,50 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
private func isProgressPaused() -> Bool {
|
||||
return self.itemProgressMode() == .pause
|
||||
}
|
||||
|
||||
private func itemProgressMode() -> StoryContentItem.ProgressMode {
|
||||
guard let component = self.component else {
|
||||
return false
|
||||
return .pause
|
||||
}
|
||||
if component.pinchState != nil {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.sendMessageContext.actionSheet != nil || self.sendMessageContext.isViewingAttachedStickers || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.privacyController != nil {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.isReporting {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.isEditingStory {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.sendMessageContext.attachmentController != nil {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.sendMessageContext.shareController != nil {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if self.sendMessageContext.tooltipScreen != nil {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
if let navigationController = component.controller()?.navigationController as? NavigationController {
|
||||
let topViewController = navigationController.topViewController
|
||||
if !(topViewController is StoryContainerScreen) && !(topViewController is MediaEditorScreen) && !(topViewController is ShareWithPeersScreen) && !(topViewController is AttachmentController) {
|
||||
return true
|
||||
return .pause
|
||||
}
|
||||
}
|
||||
if let captionItem = self.captionItem, captionItem.externalState.isExpanded {
|
||||
return true
|
||||
return .blurred
|
||||
}
|
||||
return false
|
||||
return .play
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: Transition) {
|
||||
@ -1094,13 +1098,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
itemTransition.setCornerRadius(layer: visibleItem.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / itemScale))
|
||||
itemTransition.setAlpha(view: visibleItem.contentContainerView, alpha: 1.0 * (1.0 - fractionDistanceToCenter) + 0.75 * fractionDistanceToCenter)
|
||||
|
||||
var itemProgressPaused = self.isProgressPaused()
|
||||
var itemProgressMode = self.itemProgressMode()
|
||||
if index != centralIndex {
|
||||
itemProgressPaused = true
|
||||
itemProgressMode = .pause
|
||||
}
|
||||
|
||||
if let view = view as? StoryContentItem.View {
|
||||
view.setIsProgressPaused(itemProgressPaused)
|
||||
view.setProgressMode(itemProgressMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1121,7 +1125,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
func updateIsProgressPaused() {
|
||||
let isProgressPaused = self.isProgressPaused()
|
||||
let progressMode = self.itemProgressMode()
|
||||
var centralId: Int32?
|
||||
if let component = self.component {
|
||||
centralId = component.slice.item.storyItem.id
|
||||
@ -1130,7 +1134,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
for (id, visibleItem) in self.visibleItems {
|
||||
if let view = visibleItem.view.view {
|
||||
if let view = view as? StoryContentItem.View {
|
||||
view.setIsProgressPaused(isProgressPaused || id != centralId)
|
||||
var itemMode = progressMode
|
||||
if id != centralId {
|
||||
itemMode = .pause
|
||||
}
|
||||
view.setProgressMode(itemMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2645,7 +2653,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: contentFrame.height)
|
||||
containerSize: CGSize(width: availableSize.width, height: contentFrame.height - 60.0)
|
||||
)
|
||||
captionItem.view.parentState = state
|
||||
let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize)
|
||||
|
Loading…
x
Reference in New Issue
Block a user