mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 03:21:29 +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 textStroke: (UIColor, CGFloat)?
|
||||||
public let displaySpoilers: Bool
|
public let displaySpoilers: Bool
|
||||||
public let displayEmbeddedItemsUnderSpoilers: Bool
|
public let displayEmbeddedItemsUnderSpoilers: Bool
|
||||||
|
public let customTruncationToken: NSAttributedString?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
attributedString: NSAttributedString?,
|
attributedString: NSAttributedString?,
|
||||||
@ -167,7 +168,8 @@ public final class TextNodeLayoutArguments {
|
|||||||
textShadowBlur: CGFloat? = nil,
|
textShadowBlur: CGFloat? = nil,
|
||||||
textStroke: (UIColor, CGFloat)? = nil,
|
textStroke: (UIColor, CGFloat)? = nil,
|
||||||
displaySpoilers: Bool = false,
|
displaySpoilers: Bool = false,
|
||||||
displayEmbeddedItemsUnderSpoilers: Bool = false
|
displayEmbeddedItemsUnderSpoilers: Bool = false,
|
||||||
|
customTruncationToken: NSAttributedString? = nil
|
||||||
) {
|
) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
@ -186,6 +188,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
self.textStroke = textStroke
|
self.textStroke = textStroke
|
||||||
self.displaySpoilers = displaySpoilers
|
self.displaySpoilers = displaySpoilers
|
||||||
self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers
|
self.displayEmbeddedItemsUnderSpoilers = displayEmbeddedItemsUnderSpoilers
|
||||||
|
self.customTruncationToken = customTruncationToken
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withAttributedString(_ attributedString: NSAttributedString?) -> TextNodeLayoutArguments {
|
public func withAttributedString(_ attributedString: NSAttributedString?) -> TextNodeLayoutArguments {
|
||||||
@ -206,7 +209,8 @@ public final class TextNodeLayoutArguments {
|
|||||||
textShadowBlur: self.textShadowBlur,
|
textShadowBlur: self.textShadowBlur,
|
||||||
textStroke: self.textStroke,
|
textStroke: self.textStroke,
|
||||||
displaySpoilers: self.displaySpoilers,
|
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 {
|
if let attributedString = attributedString {
|
||||||
let stringLength = attributedString.length
|
let stringLength = attributedString.length
|
||||||
|
|
||||||
@ -1168,7 +1172,17 @@ open class TextNode: ASDisplayNode {
|
|||||||
layoutSize.height += fontLineSpacing
|
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)
|
var brokenLineRange = CFRange(location: lastLineCharacterIndex, length: lineCharacterCount)
|
||||||
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||||
@ -1186,16 +1200,44 @@ open class TextNode: ASDisplayNode {
|
|||||||
lineConstrainedSize.width -= bottomCutoutSize.width
|
lineConstrainedSize.width -= bottomCutoutSize.width
|
||||||
}
|
}
|
||||||
|
|
||||||
if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(lineConstrainedSize.width) {
|
let truncatedTokenString: NSAttributedString
|
||||||
coreTextLine = originalLine
|
if let customTruncationToken {
|
||||||
|
truncatedTokenString = customTruncationToken
|
||||||
} else {
|
} else {
|
||||||
var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:]
|
var truncationTokenAttributes: [NSAttributedString.Key : AnyObject] = [:]
|
||||||
truncationTokenAttributes[NSAttributedString.Key.font] = font
|
truncationTokenAttributes[NSAttributedString.Key.font] = font
|
||||||
truncationTokenAttributes[NSAttributedString.Key(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber
|
truncationTokenAttributes[NSAttributedString.Key(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber
|
||||||
let tokenString = "\u{2026}"
|
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
|
coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(lineConstrainedSize.width), truncationType, truncationToken) ?? truncationToken
|
||||||
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
||||||
for run in runs {
|
for run in runs {
|
||||||
@ -1647,11 +1689,11 @@ open class TextNode: ASDisplayNode {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} 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
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2292,11 +2334,11 @@ open class TextView: UIView {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} 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
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1009,11 +1009,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||||
|
|
||||||
let story = item.story
|
let story = item.story
|
||||||
|
var foundItem: SparseItemGridDisplayItem?
|
||||||
var foundItemLayer: SparseItemGridLayer?
|
var foundItemLayer: SparseItemGridLayer?
|
||||||
self.itemGrid.forEachVisibleItem { item in
|
self.itemGrid.forEachVisibleItem { item in
|
||||||
guard let itemLayer = item.layer as? ItemLayer else {
|
guard let itemLayer = item.layer as? ItemLayer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
foundItem = item
|
||||||
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
if let listItem = itemLayer.item, listItem.story.id == story.id {
|
||||||
foundItemLayer = itemLayer
|
foundItemLayer = itemLayer
|
||||||
}
|
}
|
||||||
@ -1026,6 +1028,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
sourceCornerRadius: 0.0,
|
sourceCornerRadius: 0.0,
|
||||||
sourceIsAvatar: false
|
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(
|
let storyContainerScreen = StoryContainerScreen(
|
||||||
@ -1037,16 +1044,23 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var foundItem: SparseItemGridDisplayItem?
|
||||||
var foundItemLayer: SparseItemGridLayer?
|
var foundItemLayer: SparseItemGridLayer?
|
||||||
self.itemGrid.forEachVisibleItem { item in
|
self.itemGrid.forEachVisibleItem { item in
|
||||||
guard let itemLayer = item.layer as? ItemLayer else {
|
guard let itemLayer = item.layer as? ItemLayer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
foundItem = item
|
||||||
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
if let listItem = itemLayer.item, AnyHashable(listItem.story.id) == itemId {
|
||||||
foundItemLayer = itemLayer
|
foundItemLayer = itemLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let foundItemLayer {
|
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)
|
let itemRect = self.itemGrid.frameForItem(layer: foundItemLayer)
|
||||||
return StoryContainerScreen.TransitionOut(
|
return StoryContainerScreen.TransitionOut(
|
||||||
destinationView: self.view,
|
destinationView: self.view,
|
||||||
@ -1862,11 +1876,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateHiddenItems() {
|
private func updateHiddenItems() {
|
||||||
self.itemGrid.forEachVisibleItem { item in
|
self.itemGrid.forEachVisibleItem { itemValue in
|
||||||
guard let itemLayer = item.layer as? ItemLayer, let item = itemLayer.item else {
|
guard let itemLayer = itemValue.layer as? ItemLayer, let item = itemLayer.item else {
|
||||||
return
|
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 didAnimateIn: Bool = false
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
private let focusedItemPromise = Promise<StoryId?>(nil)
|
private let focusedItemPromise = Promise<StoryId?>()
|
||||||
public var focusedItem: Signal<StoryId?, NoError> {
|
public var focusedItem: Signal<StoryId?, NoError> {
|
||||||
return self.focusedItemPromise.get()
|
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 class View: UIView {
|
||||||
open func setIsProgressPaused(_ isProgressPaused: Bool) {
|
open func setProgressMode(_ progressMode: ProgressMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
open func rewind() {
|
open func rewind() {
|
||||||
|
|||||||
@ -99,23 +99,36 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
self.verticalInset = verticalInset
|
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 {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
private let scrollViewContainer: UIView
|
private let scrollViewContainer: UIView
|
||||||
private let scrollView: UIScrollView
|
private let scrollView: UIScrollView
|
||||||
|
|
||||||
|
private let collapsedText: ContentItem
|
||||||
|
private let expandedText: ContentItem
|
||||||
|
|
||||||
private let scrollMaskContainer: UIView
|
private let scrollMaskContainer: UIView
|
||||||
private let scrollFullMaskView: UIView
|
private let scrollFullMaskView: UIView
|
||||||
private let scrollCenterMaskView: UIView
|
private let scrollCenterMaskView: UIView
|
||||||
private let scrollBottomMaskView: UIImageView
|
private let scrollBottomMaskView: UIImageView
|
||||||
|
private let scrollTopMaskView: UIImageView
|
||||||
|
|
||||||
private let shadowGradientLayer: SimpleGradientLayer
|
private let shadowGradientLayer: SimpleGradientLayer
|
||||||
private let shadowPlainLayer: SimpleLayer
|
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 var component: StoryContentCaptionComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@ -125,6 +138,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
private var ignoreScrolling: Bool = false
|
private var ignoreScrolling: Bool = false
|
||||||
private var ignoreExternalState: Bool = false
|
private var ignoreExternalState: Bool = false
|
||||||
|
|
||||||
|
private var isExpanded: Bool = false
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.shadowGradientLayer = SimpleGradientLayer()
|
self.shadowGradientLayer = SimpleGradientLayer()
|
||||||
self.shadowPlainLayer = SimpleLayer()
|
self.shadowPlainLayer = SimpleLayer()
|
||||||
@ -154,6 +169,15 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
UIColor(white: 1.0, alpha: 0.0)
|
UIColor(white: 1.0, alpha: 0.0)
|
||||||
], locations: [0.0, 1.0]))
|
], locations: [0.0, 1.0]))
|
||||||
self.scrollMaskContainer.addSubview(self.scrollBottomMaskView)
|
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)
|
super.init(frame: frame)
|
||||||
|
|
||||||
@ -178,7 +202,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
if !self.bounds.contains(point) {
|
if !self.bounds.contains(point) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if let textView = self.textNode?.textNode.view {
|
|
||||||
|
if let textView = self.collapsedText.textNode?.textNode.view {
|
||||||
let textLocalPoint = self.convert(point, to: textView)
|
let textLocalPoint = self.convert(point, to: textView)
|
||||||
if textLocalPoint.y >= -7.0 {
|
if textLocalPoint.y >= -7.0 {
|
||||||
return textView
|
return textView
|
||||||
@ -190,7 +215,11 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
|
|
||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
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
|
let isExpanded = expandFraction > 0.0
|
||||||
|
|
||||||
|
self.isExpanded = isExpanded
|
||||||
|
|
||||||
if component.externalState.isExpanded != isExpanded {
|
if component.externalState.isExpanded != isExpanded {
|
||||||
component.externalState.isExpanded = isExpanded
|
component.externalState.isExpanded = isExpanded
|
||||||
|
|
||||||
@ -248,16 +279,18 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
|
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||||
|
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .ended:
|
case .ended:
|
||||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = self.textNode {
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = contentItem.textNode {
|
||||||
let titleFrame = textNode.textNode.view.bounds
|
let titleFrame = textNode.textNode.view.bounds
|
||||||
if titleFrame.contains(location) {
|
if titleFrame.contains(location) {
|
||||||
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
|
||||||
let action: Action?
|
let action: Action?
|
||||||
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
|
if case .tap = gesture, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(contentItem.dustNode?.isRevealed ?? true) {
|
||||||
let convertedPoint = recognizer.view?.convert(location, to: self.dustNode?.view) ?? location
|
let convertedPoint = recognizer.view?.convert(location, to: contentItem.dustNode?.view) ?? location
|
||||||
self.dustNode?.revealAtLocation(convertedPoint)
|
contentItem.dustNode?.revealAtLocation(convertedPoint)
|
||||||
return
|
return
|
||||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
@ -278,19 +311,24 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
action = nil
|
action = nil
|
||||||
}
|
}
|
||||||
guard let action else {
|
if let action {
|
||||||
return
|
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?) {
|
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
|
return
|
||||||
}
|
}
|
||||||
var rects: [CGRect]?
|
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 {
|
} else if let rects = rects {
|
||||||
let linkHighlightingNode: LinkHighlightingNode
|
let linkHighlightingNode: LinkHighlightingNode
|
||||||
if let current = self.linkHighlightingNode {
|
if let current = contentItem.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: UIColor(white: 1.0, alpha: 0.5))
|
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)
|
self.scrollView.insertSubview(linkHighlightingNode.view, belowSubview: textNode.textNode.view)
|
||||||
}
|
}
|
||||||
linkHighlightingNode.frame = textNode.textNode.view.frame
|
linkHighlightingNode.frame = textNode.textNode.view.frame
|
||||||
linkHighlightingNode.updateRects(rects)
|
linkHighlightingNode.updateRects(rects)
|
||||||
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
} else if let linkHighlightingNode = contentItem.linkHighlightingNode {
|
||||||
self.linkHighlightingNode = nil
|
contentItem.linkHighlightingNode = nil
|
||||||
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||||
linkHighlightingNode?.removeFromSupernode()
|
linkHighlightingNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -375,8 +415,21 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
entityFiles: component.entityFiles
|
entityFiles: component.entityFiles
|
||||||
)
|
)
|
||||||
|
|
||||||
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
let truncationToken = NSMutableAttributedString()
|
||||||
let textLayout = makeLayout(TextNodeLayoutArguments(
|
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,
|
attributedString: attributedText,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
truncationType: .end,
|
truncationType: .end,
|
||||||
@ -385,92 +438,177 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
textShadowBlur: 4.0
|
textShadowBlur: 4.0
|
||||||
))
|
))
|
||||||
|
|
||||||
let makeSpoilerLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
|
let collapsedSpoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||||
let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
if !collapsedTextLayout.0.spoilers.isEmpty {
|
||||||
if !textLayout.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))
|
||||||
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))
|
|
||||||
} else {
|
} else {
|
||||||
spoilerTextLayoutAndApply = nil
|
collapsedSpoilerTextLayoutAndApply = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxHeight: CGFloat = 50.0
|
let expandedSpoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
|
||||||
let visibleTextHeight = min(maxHeight, textLayout.0.size.height)
|
if !expandedTextLayout.0.spoilers.isEmpty {
|
||||||
let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight
|
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 scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
|
||||||
|
|
||||||
let textNode = textLayout.1(TextNodeWithEntities.Arguments(
|
do {
|
||||||
context: component.context,
|
let collapsedTextNode = collapsedTextLayout.1(TextNodeWithEntities.Arguments(
|
||||||
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(
|
|
||||||
context: component.context,
|
context: component.context,
|
||||||
cache: component.context.animationCache,
|
cache: component.context.animationCache,
|
||||||
renderer: component.context.animationRenderer,
|
renderer: component.context.animationRenderer,
|
||||||
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
||||||
attemptSynchronous: true
|
attemptSynchronous: true
|
||||||
))
|
))
|
||||||
if self.spoilerTextNode == nil {
|
if self.collapsedText.textNode !== collapsedTextNode {
|
||||||
spoilerTextNode.textNode.alpha = 0.0
|
self.collapsedText.textNode?.textNode.view.removeFromSuperview()
|
||||||
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)
|
|
||||||
|
|
||||||
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 (_, collapsedSpoilerTextApply) = collapsedSpoilerTextLayoutAndApply {
|
||||||
if let current = self.dustNode {
|
let collapsedSpoilerTextNode = collapsedSpoilerTextApply(TextNodeWithEntities.Arguments(
|
||||||
dustNode = current
|
context: component.context,
|
||||||
} else {
|
cache: component.context.animationCache,
|
||||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: component.context.sharedContext.energyUsageSettings.fullTranslucency)
|
renderer: component.context.animationRenderer,
|
||||||
self.dustNode = dustNode
|
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
|
||||||
self.scrollView.insertSubview(dustNode.view, aboveSubview: spoilerTextNode.textNode.view)
|
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 {
|
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: expandedTextLayout.0.size)
|
||||||
self.dustNode = nil
|
expandedTextNode.textNode.frame = expandedTextFrame
|
||||||
dustNode.removeFromSupernode()
|
|
||||||
|
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
|
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.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(), 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.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
|
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
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
private var unsupportedText: ComponentView<Empty>?
|
private var unsupportedText: ComponentView<Empty>?
|
||||||
private var unsupportedButton: 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 currentProgressTimer: SwiftSignalKit.Timer?
|
||||||
private var currentProgressTimerValue: Double = 0.0
|
private var currentProgressTimerValue: Double = 0.0
|
||||||
private var videoProgressDisposable: Disposable?
|
private var videoProgressDisposable: Disposable?
|
||||||
@ -96,7 +96,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.updateIsProgressPaused(update: true)
|
self.updateProgressMode(update: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
if self.videoNode != nil {
|
if self.videoNode != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.isProgressPaused {
|
if case .pause = self.progressMode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,17 @@ final class StoryItemContentComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
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
|
videoNode.ownsContentNodeUpdated = { [weak self] value in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
@ -206,10 +216,10 @@ final class StoryItemContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setIsProgressPaused(_ isProgressPaused: Bool) {
|
override func setProgressMode(_ progressMode: StoryContentItem.ProgressMode) {
|
||||||
if self.isProgressPaused != isProgressPaused {
|
if self.progressMode != progressMode {
|
||||||
self.isProgressPaused = isProgressPaused
|
self.progressMode = progressMode
|
||||||
self.updateIsProgressPaused(update: true)
|
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 {
|
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 let component = self.component {
|
||||||
if component.item.isPending {
|
if component.item.isPending {
|
||||||
canPlay = false
|
canPlay = false
|
||||||
@ -261,7 +271,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateProgressTimer() {
|
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 let component = self.component {
|
||||||
if component.item.isPending {
|
if component.item.isPending {
|
||||||
needsTimer = false
|
needsTimer = false
|
||||||
@ -274,7 +284,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
timeout: 1.0 / 60.0,
|
timeout: 1.0 / 60.0,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
completion: { [weak self] in
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +298,10 @@ final class StoryItemContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.progressMode != .play {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG && true
|
#if DEBUG && true
|
||||||
let currentProgressTimerLimit: Double = 10.0
|
let currentProgressTimerLimit: Double = 10.0
|
||||||
#else
|
#else
|
||||||
@ -626,7 +640,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.backgroundColor = UIColor(rgb: 0x181818)
|
self.backgroundColor = UIColor(rgb: 0x181818)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateIsProgressPaused(update: false)
|
self.updateProgressMode(update: false)
|
||||||
|
|
||||||
if reloadMedia && synchronousLoad {
|
if reloadMedia && synchronousLoad {
|
||||||
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
||||||
|
|||||||
@ -877,46 +877,50 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func isProgressPaused() -> Bool {
|
private func isProgressPaused() -> Bool {
|
||||||
|
return self.itemProgressMode() == .pause
|
||||||
|
}
|
||||||
|
|
||||||
|
private func itemProgressMode() -> StoryContentItem.ProgressMode {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return false
|
return .pause
|
||||||
}
|
}
|
||||||
if component.pinchState != nil {
|
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 {
|
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 {
|
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.privacyController != nil {
|
if self.privacyController != nil {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.isReporting {
|
if self.isReporting {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.isEditingStory {
|
if self.isEditingStory {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.sendMessageContext.attachmentController != nil {
|
if self.sendMessageContext.attachmentController != nil {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.sendMessageContext.shareController != nil {
|
if self.sendMessageContext.shareController != nil {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if self.sendMessageContext.tooltipScreen != nil {
|
if self.sendMessageContext.tooltipScreen != nil {
|
||||||
return true
|
return .pause
|
||||||
}
|
}
|
||||||
if let navigationController = component.controller()?.navigationController as? NavigationController {
|
if let navigationController = component.controller()?.navigationController as? NavigationController {
|
||||||
let topViewController = navigationController.topViewController
|
let topViewController = navigationController.topViewController
|
||||||
if !(topViewController is StoryContainerScreen) && !(topViewController is MediaEditorScreen) && !(topViewController is ShareWithPeersScreen) && !(topViewController is AttachmentController) {
|
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 {
|
if let captionItem = self.captionItem, captionItem.externalState.isExpanded {
|
||||||
return true
|
return .blurred
|
||||||
}
|
}
|
||||||
return false
|
return .play
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition) {
|
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.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)
|
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 {
|
if index != centralIndex {
|
||||||
itemProgressPaused = true
|
itemProgressMode = .pause
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = view as? StoryContentItem.View {
|
if let view = view as? StoryContentItem.View {
|
||||||
view.setIsProgressPaused(itemProgressPaused)
|
view.setProgressMode(itemProgressMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1121,7 +1125,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateIsProgressPaused() {
|
func updateIsProgressPaused() {
|
||||||
let isProgressPaused = self.isProgressPaused()
|
let progressMode = self.itemProgressMode()
|
||||||
var centralId: Int32?
|
var centralId: Int32?
|
||||||
if let component = self.component {
|
if let component = self.component {
|
||||||
centralId = component.slice.item.storyItem.id
|
centralId = component.slice.item.storyItem.id
|
||||||
@ -1130,7 +1134,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
for (id, visibleItem) in self.visibleItems {
|
for (id, visibleItem) in self.visibleItems {
|
||||||
if let view = visibleItem.view.view {
|
if let view = visibleItem.view.view {
|
||||||
if let view = view as? StoryContentItem.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: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: contentFrame.height)
|
containerSize: CGSize(width: availableSize.width, height: contentFrame.height - 60.0)
|
||||||
)
|
)
|
||||||
captionItem.view.parentState = state
|
captionItem.view.parentState = state
|
||||||
let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize)
|
let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user