From 6ec1f4c680908a7fc027f04a1a36284f8c39f016 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 11 Jul 2023 19:03:25 +0400 Subject: [PATCH] Stories --- submodules/Display/Source/TextNode.swift | 68 +++- .../Sources/PeerInfoStoryPaneNode.swift | 30 +- .../Sources/StoryContainerScreen.swift | 2 +- .../Sources/StoryContent.swift | 8 +- .../StoryContentCaptionComponent.swift | 369 +++++++++++++----- .../Sources/StoryItemContentComponent.swift | 40 +- .../StoryItemSetContainerComponent.swift | 46 ++- 7 files changed, 412 insertions(+), 151 deletions(-) diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 7aa5ab8860..79858fa469 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 36fca4059c..116bd2d467 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -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) + } + } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index b5a6af9573..7ebbebd468 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -1584,7 +1584,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { private var didAnimateIn: Bool = false private var isDismissed: Bool = false - private let focusedItemPromise = Promise(nil) + private let focusedItemPromise = Promise() public var focusedItem: Signal { return self.focusedItemPromise.get() } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index fa15a54460..34bd04208d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -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() { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 8623c43dd7..f029700d2d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 666c12deff..2a15c765bc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -67,7 +67,7 @@ final class StoryItemContentComponent: Component { private var unsupportedText: ComponentView? private var unsupportedButton: ComponentView? - 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") diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index f84a001833..3d64deac60 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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)