mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
549 lines
30 KiB
Swift
549 lines
30 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import Display
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import RadialStatusNode
|
|
import GalleryUI
|
|
import TelegramUniversalVideoContent
|
|
import GalleryUI
|
|
|
|
class WebSearchVideoGalleryItem: GalleryItem {
|
|
var id: AnyHashable {
|
|
return self.index
|
|
}
|
|
|
|
let index: Int
|
|
|
|
let context: AccountContext
|
|
let presentationData: PresentationData
|
|
let result: ChatContextResult
|
|
let content: UniversalVideoContent
|
|
let controllerInteraction: WebSearchGalleryControllerInteraction?
|
|
|
|
init(context: AccountContext, presentationData: PresentationData, index: Int, result: ChatContextResult, content: UniversalVideoContent, controllerInteraction: WebSearchGalleryControllerInteraction?) {
|
|
self.context = context
|
|
self.presentationData = presentationData
|
|
self.index = index
|
|
self.result = result
|
|
self.content = content
|
|
self.controllerInteraction = controllerInteraction
|
|
}
|
|
|
|
func node(synchronous: Bool) -> GalleryItemNode {
|
|
let node = WebSearchVideoGalleryItemNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction)
|
|
node.setupItem(self)
|
|
return node
|
|
}
|
|
|
|
func updateNode(node: GalleryItemNode, synchronous: Bool) {
|
|
if let node = node as? WebSearchVideoGalleryItemNode {
|
|
node.setupItem(self)
|
|
}
|
|
}
|
|
|
|
func thumbnailItem() -> (Int64, GalleryThumbnailItem)? {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private struct FetchControls {
|
|
let fetch: () -> Void
|
|
let cancel: () -> Void
|
|
}
|
|
|
|
final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|
private let context: AccountContext
|
|
private let strings: PresentationStrings
|
|
private let controllerInteraction: WebSearchGalleryControllerInteraction?
|
|
|
|
fileprivate let _ready = Promise<Void>()
|
|
|
|
private let footerContentNode: WebSearchGalleryFooterContentNode
|
|
|
|
private var videoNode: UniversalVideoNode?
|
|
private let statusButtonNode: HighlightableButtonNode
|
|
private let statusNode: RadialStatusNode
|
|
|
|
private var isCentral = false
|
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
|
private var didPause = false
|
|
private var isPaused = true
|
|
|
|
private var requiresDownload = false
|
|
|
|
var item: WebSearchVideoGalleryItem?
|
|
|
|
private let statusDisposable = MetaDisposable()
|
|
|
|
private let fetchDisposable = MetaDisposable()
|
|
private var fetchStatus: EngineMediaResource.FetchStatus?
|
|
private var fetchControls: FetchControls?
|
|
|
|
var playbackCompleted: (() -> Void)?
|
|
|
|
init(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchGalleryControllerInteraction?) {
|
|
self.context = context
|
|
self.strings = presentationData.strings
|
|
self.controllerInteraction = controllerInteraction
|
|
|
|
self.footerContentNode = WebSearchGalleryFooterContentNode(context: context, presentationData: presentationData)
|
|
|
|
self.statusButtonNode = HighlightableButtonNode()
|
|
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
|
|
|
super.init()
|
|
|
|
self.statusButtonNode.addSubnode(self.statusNode)
|
|
self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside)
|
|
|
|
self.addSubnode(self.statusButtonNode)
|
|
|
|
self.footerContentNode.cancel = {
|
|
controllerInteraction?.dismiss(true)
|
|
}
|
|
self.footerContentNode.send = { [weak self] in
|
|
if let strongSelf = self, let item = strongSelf.item {
|
|
controllerInteraction?.send(item.result)
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.statusDisposable.dispose()
|
|
}
|
|
|
|
@objc override func contentTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
|
if recognizer.state == .ended {
|
|
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
|
switch gesture {
|
|
case .tap:
|
|
if let item = self.item, let selectionState = item.controllerInteraction?.selectionState {
|
|
let legacyItem = legacyWebSearchItem(account: item.context.account, result: item.result)
|
|
selectionState.toggleItemSelection(legacyItem, success: nil)
|
|
}
|
|
case .doubleTap:
|
|
super.contentTap(recognizer)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func ready() -> Signal<Void, NoError> {
|
|
return self._ready.get()
|
|
}
|
|
|
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
|
|
|
self.validLayout = (layout, navigationBarHeight)
|
|
|
|
let statusDiameter: CGFloat = 50.0
|
|
let statusFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusDiameter) / 2.0), y: floor((layout.size.height - statusDiameter) / 2.0)), size: CGSize(width: statusDiameter, height: statusDiameter))
|
|
transition.updateFrame(node: self.statusButtonNode, frame: statusFrame)
|
|
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusFrame.size))
|
|
}
|
|
|
|
func setupItem(_ item: WebSearchVideoGalleryItem) {
|
|
if self.item?.content.id != item.content.id {
|
|
var isAnimated = false
|
|
var mediaResource: EngineMediaResource?
|
|
if let content = item.content as? NativeVideoContent {
|
|
isAnimated = content.fileReference.media.isAnimated
|
|
mediaResource = EngineMediaResource(content.fileReference.media.resource)
|
|
}
|
|
|
|
if let videoNode = self.videoNode {
|
|
videoNode.canAttachContent = false
|
|
videoNode.removeFromSupernode()
|
|
}
|
|
|
|
let mediaManager = item.context.sharedContext.mediaManager
|
|
|
|
let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery)
|
|
let videoSize = CGSize(width: item.content.dimensions.width * 2.0, height: item.content.dimensions.height * 2.0)
|
|
videoNode.updateLayout(size: videoSize, transition: .immediate)
|
|
self.videoNode = videoNode
|
|
videoNode.isUserInteractionEnabled = false
|
|
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
|
|
videoNode.canAttachContent = true
|
|
|
|
self.requiresDownload = true
|
|
var mediaFileStatus: Signal<EngineMediaResource.FetchStatus?, NoError> = .single(nil)
|
|
if let mediaResource = mediaResource {
|
|
mediaFileStatus = item.context.account.postbox.mediaBox.resourceStatus(mediaResource._asResource())
|
|
|> map { status in
|
|
return EngineMediaResource.FetchStatus(status)
|
|
}
|
|
|> map(Optional.init)
|
|
}
|
|
|
|
self.statusDisposable.set((combineLatest(videoNode.status, mediaFileStatus)
|
|
|> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in
|
|
if let strongSelf = self {
|
|
var initialBuffering = false
|
|
var isPaused = true
|
|
if let value = value {
|
|
if let zoomableContent = strongSelf.zoomableContent, !value.dimensions.width.isZero && !value.dimensions.height.isZero {
|
|
let videoSize = CGSize(width: value.dimensions.width * 2.0, height: value.dimensions.height * 2.0)
|
|
if !zoomableContent.0.equalTo(videoSize) {
|
|
strongSelf.zoomableContent = (videoSize, zoomableContent.1)
|
|
strongSelf.videoNode?.updateLayout(size: videoSize, transition: .immediate)
|
|
}
|
|
}
|
|
switch value.status {
|
|
case .playing:
|
|
isPaused = false
|
|
case let .buffering(_, whilePlaying, _, _):
|
|
initialBuffering = true
|
|
isPaused = !whilePlaying
|
|
var isStreaming = false
|
|
if let fetchStatus = strongSelf.fetchStatus {
|
|
switch fetchStatus {
|
|
case .Local:
|
|
break
|
|
default:
|
|
isStreaming = true
|
|
}
|
|
}
|
|
if let content = item.content as? NativeVideoContent, !isStreaming {
|
|
initialBuffering = false
|
|
if !content.enableSound {
|
|
isPaused = false
|
|
}
|
|
}
|
|
default:
|
|
if let content = item.content as? NativeVideoContent, !content.streamVideo.enabled {
|
|
if !content.enableSound {
|
|
isPaused = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var fetching = false
|
|
if initialBuffering {
|
|
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {})
|
|
} else {
|
|
var state: RadialStatusNodeState = .none
|
|
|
|
if let fetchStatus = fetchStatus {
|
|
if strongSelf.requiresDownload {
|
|
switch fetchStatus {
|
|
case let .Fetching(_, progress):
|
|
fetching = true
|
|
isPaused = true
|
|
state = .progress(color: .white, lineWidth: nil, value: CGFloat(max(0.027, progress)), cancelEnabled: false, animateRotation: true)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
strongSelf.statusNode.transitionToState(state, animated: false, completion: {})
|
|
}
|
|
|
|
strongSelf.isPaused = isPaused
|
|
strongSelf.fetchStatus = fetchStatus
|
|
|
|
strongSelf.statusButtonNode.isHidden = !initialBuffering && !isPaused && !fetching
|
|
}
|
|
}))
|
|
|
|
self.zoomableContent = (videoSize, videoNode)
|
|
|
|
videoNode.playbackCompleted = { [weak videoNode] in
|
|
Queue.mainQueue().async {
|
|
if !isAnimated {
|
|
videoNode?.seek(0.0)
|
|
}
|
|
}
|
|
}
|
|
|
|
self._ready.set(videoNode.ready)
|
|
}
|
|
|
|
self.item = item
|
|
}
|
|
|
|
override func centralityUpdated(isCentral: Bool) {
|
|
super.centralityUpdated(isCentral: isCentral)
|
|
|
|
if self.isCentral != isCentral {
|
|
self.isCentral = isCentral
|
|
|
|
if let videoNode = self.videoNode, videoNode.ownsContentNode {
|
|
if isCentral {
|
|
videoNode.play()
|
|
} else {
|
|
videoNode.pause()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func activateAsInitial() {
|
|
if self.isCentral {
|
|
self.videoNode?.play()
|
|
}
|
|
}
|
|
|
|
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
|
guard let videoNode = self.videoNode else {
|
|
return
|
|
}
|
|
|
|
if let node = node.0 as? OverlayMediaItemNode {
|
|
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
|
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
|
|
|
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
|
|
transformedFrame.origin = CGPoint()
|
|
|
|
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
|
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
|
|
|
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
|
} else {
|
|
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
|
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
|
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
|
let transformedCopyViewFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
|
|
|
let surfaceCopyView = node.2().0!
|
|
let copyView = node.2().0!
|
|
|
|
addToTransitionSurface(surfaceCopyView)
|
|
|
|
var transformedSurfaceFrame: CGRect?
|
|
var transformedSurfaceFinalFrame: CGRect?
|
|
if let contentSurface = surfaceCopyView.superview {
|
|
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
|
transformedSurfaceFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface)
|
|
}
|
|
|
|
if let transformedSurfaceFrame = transformedSurfaceFrame {
|
|
surfaceCopyView.frame = transformedSurfaceFrame
|
|
}
|
|
|
|
self.view.insertSubview(copyView, belowSubview: self.scrollNode.view)
|
|
copyView.frame = transformedSelfFrame
|
|
|
|
copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
|
|
|
surfaceCopyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
|
|
|
copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in
|
|
copyView?.removeFromSuperview()
|
|
})
|
|
let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height)
|
|
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
|
|
|
if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame {
|
|
surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in
|
|
surfaceCopyView?.removeFromSuperview()
|
|
})
|
|
let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height)
|
|
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
|
}
|
|
|
|
videoNode.allowsGroupOpacity = true
|
|
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
|
|
videoNode?.allowsGroupOpacity = false
|
|
})
|
|
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
|
|
transformedFrame.origin = CGPoint()
|
|
|
|
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
|
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
|
|
|
self.statusButtonNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusButtonNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
self.statusButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
self.statusButtonNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
}
|
|
}
|
|
|
|
override func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
|
guard let videoNode = self.videoNode else {
|
|
completion()
|
|
return
|
|
}
|
|
|
|
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
|
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
|
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
|
let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
|
|
|
var positionCompleted = false
|
|
var boundsCompleted = false
|
|
var copyCompleted = false
|
|
|
|
let copyView = node.2().0!
|
|
let surfaceCopyView = node.2().0!
|
|
|
|
addToTransitionSurface(surfaceCopyView)
|
|
|
|
var transformedSurfaceFrame: CGRect?
|
|
var transformedSurfaceCopyViewInitialFrame: CGRect?
|
|
if let contentSurface = surfaceCopyView.superview {
|
|
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
|
transformedSurfaceCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface)
|
|
}
|
|
|
|
self.view.insertSubview(copyView, belowSubview: self.scrollNode.view)
|
|
copyView.frame = transformedSelfFrame
|
|
|
|
let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in
|
|
if positionCompleted && boundsCompleted && copyCompleted {
|
|
copyView?.removeFromSuperview()
|
|
surfaceCopyView?.removeFromSuperview()
|
|
completion()
|
|
}
|
|
}
|
|
|
|
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
|
|
surfaceCopyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
|
|
|
copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
|
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height)
|
|
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
copyCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedCopyViewInitialFrame = transformedSurfaceCopyViewInitialFrame {
|
|
surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
|
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSurfaceFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSurfaceFrame.size.height)
|
|
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
|
}
|
|
|
|
videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
|
positionCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
videoNode.allowsGroupOpacity = true
|
|
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
|
|
videoNode?.allowsGroupOpacity = false
|
|
})
|
|
|
|
self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
|
//positionCompleted = true
|
|
//intermediateCompletion()
|
|
})
|
|
self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
|
self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
|
|
|
|
transformedFrame.origin = CGPoint()
|
|
|
|
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
|
videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
boundsCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
}
|
|
|
|
func animateOut(toOverlay node: ASDisplayNode, completion: @escaping () -> Void) {
|
|
guard let videoNode = self.videoNode else {
|
|
completion()
|
|
return
|
|
}
|
|
|
|
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
|
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
|
let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view)
|
|
let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
|
let transformedSelfTargetSuperFrame = videoNode.view.convert(videoNode.view.bounds, to: node.view.superview)
|
|
|
|
var positionCompleted = false
|
|
var boundsCompleted = false
|
|
var copyCompleted = false
|
|
var nodeCompleted = false
|
|
|
|
let copyView = node.view.snapshotContentTree()!
|
|
|
|
videoNode.isHidden = true
|
|
copyView.frame = transformedSelfFrame
|
|
|
|
let intermediateCompletion = { [weak copyView] in
|
|
if positionCompleted && boundsCompleted && copyCompleted && nodeCompleted {
|
|
copyView?.removeFromSuperview()
|
|
completion()
|
|
}
|
|
}
|
|
|
|
copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
|
|
|
copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
|
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height)
|
|
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
copyCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
|
positionCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
|
|
|
self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
|
//positionCompleted = true
|
|
//intermediateCompletion()
|
|
})
|
|
self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
|
self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
|
|
|
|
transformedFrame.origin = CGPoint()
|
|
|
|
let videoTransform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
|
videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: videoTransform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
boundsCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
let nodeTransform = CATransform3DScale(node.layer.transform, videoNode.layer.bounds.size.width / transformedFrame.size.width, videoNode.layer.bounds.size.height / transformedFrame.size.height, 1.0)
|
|
node.layer.animatePosition(from: CGPoint(x: transformedSelfTargetSuperFrame.midX, y: transformedSelfTargetSuperFrame.midY), to: node.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
|
node.layer.animate(from: NSValue(caTransform3D: nodeTransform), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
nodeCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
}
|
|
|
|
@objc func statusButtonPressed() {
|
|
if let videoNode = self.videoNode {
|
|
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
|
|
self.toggleControlsVisibility()
|
|
}
|
|
|
|
if let fetchStatus = self.fetchStatus {
|
|
switch fetchStatus {
|
|
case .Local:
|
|
videoNode.togglePlayPause()
|
|
case .Remote, .Paused:
|
|
if self.requiresDownload {
|
|
self.fetchControls?.fetch()
|
|
} else {
|
|
videoNode.togglePlayPause()
|
|
}
|
|
case .Fetching:
|
|
self.fetchControls?.cancel()
|
|
}
|
|
} else {
|
|
videoNode.togglePlayPause()
|
|
}
|
|
}
|
|
}
|
|
|
|
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
|
return .single((self.footerContentNode, nil))
|
|
}
|
|
}
|