Initial instant video expansion implementation

This commit is contained in:
Ilya Laktyushin
2021-07-18 00:22:37 +03:00
parent 6b3b6ac9ab
commit 7a34781f6f
21 changed files with 261 additions and 81 deletions

View File

@@ -83,6 +83,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
private var animating = false
override init() {
self.secretVideoPlaceholderBackground = ASImageNode()
self.secretVideoPlaceholderBackground.isLayerBacked = true
@@ -130,7 +132,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
self.view.addGestureRecognizer(recognizer)
}
func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) {
func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) {
let previousFile = self.media
let currentItem = self.item
@@ -138,7 +140,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout()
return { item, width, displaySize, statusDisplayType, automaticDownload in
return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusDisplayType, automaticDownload in
var secretVideoPlaceholderBackgroundImage: UIImage?
var updatedInfoBackgroundImage: UIImage?
var updatedMuteIconImage: UIImage?
@@ -163,7 +165,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
secretVideoPlaceholderBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(theme.theme, wallpaper: !theme.wallpaper.isEmpty)
}
let imageSize = displaySize
let imageSize = maximumDisplaySize
let imageScale = displaySize.width / maximumDisplaySize.width
let updatedMessageId = item.message.id != currentItem?.message.id
@@ -294,20 +297,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
var contentSize = imageSize
var displayVideoFrame = videoFrame
displayVideoFrame.size.width *= imageScale
displayVideoFrame.size.height *= imageScale
var contentSize = displayVideoFrame.size
var dateAndStatusOverflow = false
if case .bubble = statusDisplayType, videoFrame.maxX + dateAndStatusSize.width > width {
if case .bubble = statusDisplayType, displayVideoFrame.maxX + dateAndStatusSize.width > width {
contentSize.height += dateAndStatusSize.height + 2.0
contentSize.width = max(contentSize.width, dateAndStatusSize.width)
dateAndStatusOverflow = true
}
let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floor(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width)))
let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width)))
return (result, { [weak self] layoutData, transition in
if let strongSelf = self {
strongSelf.item = item
strongSelf.videoFrame = videoFrame
strongSelf.videoFrame = displayVideoFrame
strongSelf.secretProgressIcon = secretProgressIcon
strongSelf.automaticDownload = automaticDownload
@@ -327,10 +334,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image {
let infoWidth = muteImage.size.width
let infoBackgroundFrame = CGRect(origin: CGPoint(x: floor(videoFrame.minX + (videoFrame.size.width - infoWidth) / 2.0), y: videoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height))
transition.updateFrame(node: strongSelf.infoBackgroundNode, frame: infoBackgroundFrame)
let infoBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(displayVideoFrame.minX + (displayVideoFrame.size.width - infoWidth) / 2.0), y: displayVideoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height))
strongSelf.infoBackgroundNode.frame = infoBackgroundFrame
let muteIconFrame = CGRect(origin: CGPoint(x: infoBackgroundFrame.width - muteImage.size.width, y: 0.0), size: muteImage.size)
transition.updateFrame(node: strongSelf.muteIconNode, frame: muteIconFrame)
strongSelf.muteIconNode.frame = muteIconFrame
}
if let updatedFile = updatedFile, updatedMedia {
@@ -340,21 +347,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.fetchedThumbnailDisposable.set(nil)
}
}
dateAndStatusApply(false)
switch layoutData {
case let .unconstrained(width):
let dateAndStatusOrigin: CGPoint
if dateAndStatusOverflow {
dateAndStatusOrigin = CGPoint(x: videoFrame.minX - 4.0, y: videoFrame.maxY + 2.0)
dateAndStatusOrigin = CGPoint(x: displayVideoFrame.minX - 4.0, y: displayVideoFrame.maxY + 2.0)
} else {
dateAndStatusOrigin = CGPoint(x: min(floor(videoFrame.midX) + 55.0, width - dateAndStatusSize.width - 4.0), y: videoFrame.height - dateAndStatusSize.height)
dateAndStatusOrigin = CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, width - dateAndStatusSize.width - 4.0), y: displayVideoFrame.height - dateAndStatusSize.height)
}
strongSelf.dateAndStatusNode.frame = CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize)
case let .constrained(_, right):
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize)
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize)
}
var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
if let telegramFile = updatedFile {
if updatedMedia {
@@ -471,7 +478,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
if let durationNode = strongSelf.durationNode {
durationNode.frame = CGRect(origin: CGPoint(x: videoFrame.midX - 56.0, y: videoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0))
durationNode.frame = CGRect(origin: CGPoint(x: displayVideoFrame.midX - 56.0 - 25.0 * scaleProgress, y: displayVideoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0))
durationNode.isSeen = !notConsumed
let size = durationNode.size
if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 {
@@ -481,12 +488,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
if let videoNode = strongSelf.videoNode {
videoNode.frame = videoFrame
videoNode.bounds = CGRect(origin: CGPoint(), size: videoFrame.size)
videoNode.transform = CATransform3DMakeScale(imageScale, imageScale, 1.0)
videoNode.position = displayVideoFrame.center
videoNode.updateLayout(size: arguments.boundingSize, transition: .immediate)
}
strongSelf.secretVideoPlaceholderBackground.frame = videoFrame
strongSelf.secretVideoPlaceholderBackground.frame = displayVideoFrame
let placeholderFrame = videoFrame.insetBy(dx: 2.0, dy: 2.0)
let placeholderFrame = displayVideoFrame.insetBy(dx: 2.0, dy: 2.0)
strongSelf.secretVideoPlaceholder.frame = placeholderFrame
let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout()
let arguments = TransformImageArguments(corners: ImageCorners(radius: placeholderFrame.size.width / 2.0), imageSize: placeholderFrame.size, boundingSize: placeholderFrame.size, intrinsicInsets: UIEdgeInsets())
@@ -599,7 +608,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if self.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor)
self.isUserInteractionEnabled = false
statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floor((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floor((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0))
statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floorToScreenPixels((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floorToScreenPixels((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0))
self.statusNode = statusNode
self.addSubnode(statusNode)
}
@@ -671,7 +680,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let current = self.playbackStatusNode {
playbackStatusNode = current
} else {
playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8))
playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6))
self.addSubnode(playbackStatusNode)
self.playbackStatusNode = playbackStatusNode
}
@@ -800,16 +809,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return nil
}
static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) {
static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) {
let makeLayout = node?.asyncLayout()
return { item, width, displaySize, statusType, automaticDownload in
return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload in
var createdNode: ChatMessageInteractiveInstantVideoNode?
let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void)
if let makeLayout = makeLayout {
sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType, automaticDownload)
sizeAndApplyLayout = makeLayout(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload)
} else {
let node = ChatMessageInteractiveInstantVideoNode()
sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType, automaticDownload)
sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload)
createdNode = node
}
return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in