mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-26 14:01:24 +00:00
Added Lottie animation viewer
This commit is contained in:
22
Images.xcassets/Media Gallery/SlowDown.imageset/Contents.json
vendored
Normal file
22
Images.xcassets/Media Gallery/SlowDown.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "SlowDown@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "SlowDown@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Media Gallery/SlowDown.imageset/SlowDown@2x.png
vendored
Normal file
BIN
Images.xcassets/Media Gallery/SlowDown.imageset/SlowDown@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 727 B |
BIN
Images.xcassets/Media Gallery/SlowDown.imageset/SlowDown@3x.png
vendored
Normal file
BIN
Images.xcassets/Media Gallery/SlowDown.imageset/SlowDown@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -11,6 +11,7 @@
|
||||
0900678F21ED8E0E00530762 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900678E21ED8E0E00530762 /* HexColor.swift */; };
|
||||
0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838721931D960067EFBD /* LanguageSuggestionController.swift */; };
|
||||
0902838D2194AEB90067EFBD /* ImageTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838C2194AEB90067EFBD /* ImageTransparency.swift */; };
|
||||
090A22172273713000694CB0 /* ChatAnimationGalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090A22162273713000694CB0 /* ChatAnimationGalleryItem.swift */; };
|
||||
090B48C82200BCA8005083FA /* WallpaperUploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090B48C72200BCA8005083FA /* WallpaperUploadManager.swift */; };
|
||||
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
|
||||
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
||||
@@ -1193,6 +1194,7 @@
|
||||
0900678E21ED8E0E00530762 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = "<group>"; };
|
||||
0902838721931D960067EFBD /* LanguageSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSuggestionController.swift; sourceTree = "<group>"; };
|
||||
0902838C2194AEB90067EFBD /* ImageTransparency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransparency.swift; sourceTree = "<group>"; };
|
||||
090A22162273713000694CB0 /* ChatAnimationGalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAnimationGalleryItem.swift; sourceTree = "<group>"; };
|
||||
090B48C72200BCA8005083FA /* WallpaperUploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperUploadManager.swift; sourceTree = "<group>"; };
|
||||
090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = "<group>"; };
|
||||
090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = "<group>"; };
|
||||
@@ -4844,6 +4846,7 @@
|
||||
D0104F2B1F471EEB004E4881 /* InstantPageGalleryFooterContentNode.swift */,
|
||||
D0A8BBA01F61EE83000F03FD /* UniversalVideoGalleryItem.swift */,
|
||||
09F79A0221C8225600820234 /* WebSearchVideoGalleryItem.swift */,
|
||||
090A22162273713000694CB0 /* ChatAnimationGalleryItem.swift */,
|
||||
);
|
||||
name = Items;
|
||||
sourceTree = "<group>";
|
||||
@@ -5556,6 +5559,7 @@
|
||||
09CE95002232729A00A7D2C3 /* StickerPaneSearchContentNode.swift in Sources */,
|
||||
D053DADC201AAAB100993D32 /* ChatTextInputMenu.swift in Sources */,
|
||||
0962E66321B3513100245FD9 /* WebSearchControllerNode.swift in Sources */,
|
||||
090A22172273713000694CB0 /* ChatAnimationGalleryItem.swift in Sources */,
|
||||
D0EC6D1A1EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */,
|
||||
D0EC6D1B1EB9F58800EBF1C3 /* FFMpegMediaVideoFrameDecoder.swift in Sources */,
|
||||
D01C06AF1FBB461E001561AB /* JoinLinkPreviewController.swift in Sources */,
|
||||
|
||||
@@ -4,17 +4,26 @@ import Lottie
|
||||
|
||||
final class AnimationNode : ASDisplayNode {
|
||||
private let scale: CGFloat
|
||||
var speed: CGFloat = 1.0 {
|
||||
didSet {
|
||||
if let animationView = animationView() {
|
||||
animationView.animationSpeed = speed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var played = false
|
||||
var completion: (() -> Void)?
|
||||
|
||||
init(animation: String, keysToColor: [String]?, color: UIColor, scale: CGFloat) {
|
||||
init(animation: String? = nil, keysToColor: [String]? = nil, color: UIColor = .black, scale: CGFloat = 1.0) {
|
||||
self.scale = scale
|
||||
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
if let url = frameworkBundle.url(forResource: animation, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||
if let animation = animation, let url = frameworkBundle.url(forResource: animation, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||
let view = LOTAnimationView(model: composition, in: frameworkBundle)
|
||||
view.animationSpeed = self.speed
|
||||
view.backgroundColor = .clear
|
||||
view.isOpaque = false
|
||||
|
||||
@@ -27,11 +36,21 @@ final class AnimationNode : ASDisplayNode {
|
||||
|
||||
return view
|
||||
} else {
|
||||
return UIView()
|
||||
return LOTAnimationView()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setAnimation(name: String) {
|
||||
if let url = frameworkBundle.url(forResource: name, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||
self.animationView()?.sceneModel = composition
|
||||
}
|
||||
}
|
||||
|
||||
func setAnimation(json: [AnyHashable: Any]) {
|
||||
self.animationView()?.setAnimation(json: json)
|
||||
}
|
||||
|
||||
func animationView() -> LOTAnimationView? {
|
||||
return self.view as? LOTAnimationView
|
||||
}
|
||||
@@ -45,6 +64,13 @@ final class AnimationNode : ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func loop() {
|
||||
if let animationView = animationView() {
|
||||
animationView.loopAnimation = true
|
||||
animationView.play()
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
if self.played, let animationView = animationView() {
|
||||
self.played = false
|
||||
|
||||
339
TelegramUI/ChatAnimationGalleryItem.swift
Normal file
339
TelegramUI/ChatAnimationGalleryItem.swift
Normal file
@@ -0,0 +1,339 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Lottie
|
||||
|
||||
class ChatAnimationGalleryItem: GalleryItem {
|
||||
let context: AccountContext
|
||||
let presentationData: PresentationData
|
||||
let message: Message
|
||||
let location: MessageHistoryEntryLocation?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.message = message
|
||||
self.location = location
|
||||
}
|
||||
|
||||
func node() -> GalleryItemNode {
|
||||
let node = ChatAnimationGalleryItemNode(context: self.context, presentationData: self.presentationData)
|
||||
|
||||
for media in self.message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
node.setFile(context: self.context, fileReference: .message(message: MessageReference(self.message), media: file))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
node.setMessage(self.message)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? ChatAnimationGalleryItemNode {
|
||||
node.setMessage(self.message)
|
||||
}
|
||||
}
|
||||
|
||||
func thumbnailItem() -> (Int64, GalleryThumbnailItem)? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var backgroundButtonIcon: UIImage = {
|
||||
return generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setLineWidth(1.0)
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 0.5, dy: 0.5))
|
||||
|
||||
context.addEllipse(in: bounds.insetBy(dx: 0.5, dy: 0.5))
|
||||
context.clip()
|
||||
|
||||
context.fill(CGRect(x: 0.0, y: 0.0, width: 10.0, height: 20.0))
|
||||
})!
|
||||
}()
|
||||
|
||||
final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let context: AccountContext
|
||||
private var message: Message?
|
||||
|
||||
fileprivate let _title = Promise<String>()
|
||||
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private let animationNode: AnimationNode
|
||||
|
||||
private let statusNodeContainer: HighlightableButtonNode
|
||||
private let statusNode: RadialStatusNode
|
||||
private let footerContentNode: ChatItemGalleryFooterContentNode
|
||||
|
||||
private var contextAndMedia: (AccountContext, AnyMediaReference)?
|
||||
|
||||
private var disposable = MetaDisposable()
|
||||
private var fetchDisposable = MetaDisposable()
|
||||
private let statusDisposable = MetaDisposable()
|
||||
private var status: MediaResourceStatus?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData) {
|
||||
self.context = context
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.backgroundColor = .black
|
||||
|
||||
self.animationNode = AnimationNode()
|
||||
self.containerNode.addSubnode(self.animationNode)
|
||||
|
||||
self.footerContentNode = ChatItemGalleryFooterContentNode(context: context, presentationData: presentationData)
|
||||
|
||||
self.statusNodeContainer = HighlightableButtonNode()
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
|
||||
self.statusNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.statusNodeContainer.addSubnode(self.statusNode)
|
||||
self.addSubnode(self.statusNodeContainer)
|
||||
|
||||
self.statusNodeContainer.addTarget(self, action: #selector(self.statusPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.statusNodeContainer.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.fetchDisposable.dispose()
|
||||
self.statusDisposable.dispose()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
let statusSize = CGSize(width: 50.0, height: 50.0)
|
||||
transition.updateFrame(node: self.statusNodeContainer, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: floor((layout.size.height - statusSize.height) / 2.0)), size: statusSize))
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
|
||||
}
|
||||
|
||||
fileprivate func setMessage(_ message: Message) {
|
||||
self.footerContentNode.setMessage(message)
|
||||
}
|
||||
|
||||
func setFile(context: AccountContext, fileReference: FileMediaReference) {
|
||||
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
|
||||
let signal = chatMessageAnimationData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|
||||
|> mapToSignal { data, completed -> Signal<Data, NoError> in
|
||||
if completed, let data = data {
|
||||
return .single(data)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] next in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let object = try? JSONSerialization.jsonObject(with: next, options: []) as? [AnyHashable: Any], let json = object {
|
||||
let containerSize = CGSize(width: 640.0, height: 640.0)
|
||||
strongSelf.animationNode.setAnimation(json: json)
|
||||
strongSelf.zoomableContent = (containerSize, strongSelf.containerNode)
|
||||
|
||||
if let animationSize = strongSelf.animationNode.preferredSize() {
|
||||
let size = animationSize.fitted(containerSize)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((containerSize.width - size.width) / 2.0), y: floor((containerSize.height - size.height) / 2.0)), size: size)
|
||||
}
|
||||
|
||||
strongSelf.animationNode.loop()
|
||||
}
|
||||
}))
|
||||
self.setupStatus(resource: fileReference.media.resource)
|
||||
|
||||
let speedItem = UIBarButtonItem(image: UIImage(bundleImageName: "Media Gallery/SlowDown"), style: .plain, target: self, action: #selector(self.toggleSpeedButtonPressed))
|
||||
let backgroundItem = UIBarButtonItem(image: backgroundButtonIcon, style: .plain, target: self, action: #selector(self.toggleBackgroundButtonPressed))
|
||||
self._rightBarButtonItems.set(.single([speedItem, backgroundItem]))
|
||||
}
|
||||
self.contextAndMedia = (context, fileReference.abstract)
|
||||
}
|
||||
|
||||
@objc private func toggleSpeedButtonPressed() {
|
||||
if self.animationNode.speed == 1.0 {
|
||||
self.animationNode.speed = 0.1
|
||||
} else {
|
||||
self.animationNode.speed = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func toggleBackgroundButtonPressed() {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
if self.containerNode.backgroundColor == .white {
|
||||
transition.updateBackgroundColor(node: self.containerNode, color: .black)
|
||||
} else {
|
||||
transition.updateBackgroundColor(node: self.containerNode, color: .white)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupStatus(resource: MediaResource) {
|
||||
self.statusDisposable.set((self.context.account.postbox.mediaBox.resourceStatus(resource)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
let previousStatus = strongSelf.status
|
||||
strongSelf.status = status
|
||||
switch status {
|
||||
case .Remote:
|
||||
strongSelf.statusNode.isHidden = false
|
||||
strongSelf.statusNode.alpha = 1.0
|
||||
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
|
||||
strongSelf.statusNode.transitionToState(.download(.white), completion: {})
|
||||
case let .Fetching(isActive, progress):
|
||||
strongSelf.statusNode.isHidden = false
|
||||
strongSelf.statusNode.alpha = 1.0
|
||||
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true), completion: {})
|
||||
case .Local:
|
||||
if let previousStatus = previousStatus, case .Fetching = previousStatus {
|
||||
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: {
|
||||
if let strongSelf = self {
|
||||
strongSelf.statusNode.alpha = 0.0
|
||||
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
|
||||
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if !strongSelf.statusNode.isHidden && !strongSelf.statusNode.alpha.isZero {
|
||||
strongSelf.statusNode.alpha = 0.0
|
||||
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
|
||||
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
override func animateIn(from node: (ASDisplayNode, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view.superview)
|
||||
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.containerNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let transform = CATransform3DScale(self.containerNode.layer.transform, transformedFrame.size.width / self.containerNode.layer.bounds.size.width, transformedFrame.size.height / self.containerNode.layer.bounds.size.height, 1.0)
|
||||
self.containerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: self.containerNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
||||
|
||||
self.statusNodeContainer.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusNodeContainer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusNodeContainer.layer.animateScale(from: 0.5, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
override func animateOut(to node: (ASDisplayNode, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
let transformedCopyViewInitialFrame = self.containerNode.view.convert(self.containerNode.view.bounds, to: self.view)
|
||||
|
||||
var positionCompleted = false
|
||||
var boundsCompleted = false
|
||||
var copyCompleted = false
|
||||
|
||||
let (maybeCopyView, copyViewBackgrond) = node.1()
|
||||
copyViewBackgrond?.alpha = 0.0
|
||||
let copyView = maybeCopyView!
|
||||
|
||||
self.view.insertSubview(copyView, belowSubview: self.containerNode.view)
|
||||
copyView.frame = transformedSelfFrame
|
||||
|
||||
let intermediateCompletion = { [weak copyView] in
|
||||
if positionCompleted && boundsCompleted && copyCompleted {
|
||||
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()
|
||||
})
|
||||
|
||||
self.containerNode.layer.animatePosition(from: self.containerNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
positionCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let transform = CATransform3DScale(self.containerNode.layer.transform, transformedFrame.size.width / self.containerNode.layer.bounds.size.width, transformedFrame.size.height / self.containerNode.layer.bounds.size.height, 1.0)
|
||||
self.containerNode.layer.animate(from: NSValue(caTransform3D: self.containerNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
boundsCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
self.statusNodeContainer.layer.animatePosition(from: self.statusNodeContainer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.statusNodeContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override func visibilityUpdated(isVisible: Bool) {
|
||||
super.visibilityUpdated(isVisible: isVisible)
|
||||
|
||||
if let (context, mediaReference) = self.contextAndMedia, let fileReference = mediaReference.concrete(TelegramMediaFile.self) {
|
||||
if isVisible {
|
||||
} else {
|
||||
self.fetchDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func title() -> Signal<String, NoError> {
|
||||
return self._title.get()
|
||||
}
|
||||
|
||||
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
|
||||
return self._rightBarButtonItems.get()
|
||||
}
|
||||
|
||||
override func footerContent() -> Signal<GalleryFooterContentNode?, NoError> {
|
||||
return .single(self.footerContentNode)
|
||||
}
|
||||
|
||||
@objc func statusPressed() {
|
||||
if let (_, mediaReference) = self.contextAndMedia, let status = self.status {
|
||||
var resource: MediaResourceReference?
|
||||
var statsCategory: MediaResourceStatsCategory?
|
||||
if let fileReference = mediaReference.concrete(TelegramMediaFile.self) {
|
||||
resource = fileReference.resourceReference(fileReference.media.resource)
|
||||
statsCategory = statsCategoryForFileWithAttributes(fileReference.media.attributes)
|
||||
}
|
||||
if let resource = resource {
|
||||
switch status {
|
||||
case .Fetching:
|
||||
self.context.account.postbox.mediaBox.cancelInteractiveResourceFetch(resource.resource)
|
||||
case .Remote:
|
||||
self.fetchDisposable.set(fetchedMediaResource(postbox: self.context.account.postbox, reference: resource, statsCategory: statsCategory ?? .generic).start())
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let statusDisposable = MetaDisposable()
|
||||
private var status: MediaResourceStatus?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData,performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) {
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) {
|
||||
self.context = context
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
@@ -141,7 +141,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
for media in item.message.media {
|
||||
if let telegramFile = media as? TelegramMediaFile {
|
||||
if self.telegramFile != telegramFile {
|
||||
let signal = chatMessageAnimatedStickerDatas(postbox: item.context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: telegramFile), synchronousLoad: false)
|
||||
let signal = chatMessageAnimationData(postbox: item.context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: telegramFile), synchronousLoad: false)
|
||||
|> mapToSignal { data, completed -> Signal<Data, NoError> in
|
||||
if completed, let data = data {
|
||||
return .single(data)
|
||||
|
||||
@@ -166,7 +166,10 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation
|
||||
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, timecode: timecode, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions)
|
||||
} else {
|
||||
if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" {
|
||||
if let fileName = file.fileName, (fileName as NSString).pathExtension.lowercased() == "json" {
|
||||
return ChatAnimationGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
|
||||
}
|
||||
else if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" {
|
||||
var pixelsCount: Int = 0
|
||||
if let dimensions = file.dimensions {
|
||||
pixelsCount = Int(dimensions.width) * Int(dimensions.height)
|
||||
@@ -317,6 +320,7 @@ class GalleryController: ViewController {
|
||||
private let centralItemTitle = Promise<String>()
|
||||
private let centralItemTitleView = Promise<UIView?>()
|
||||
private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>()
|
||||
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>()
|
||||
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
|
||||
private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>()
|
||||
private let centralItemAttributesDisposable = DisposableSet();
|
||||
@@ -501,6 +505,10 @@ class GalleryController: ViewController {
|
||||
self?.navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
}))
|
||||
|
||||
self.centralItemAttributesDisposable.add(self.centralItemRightBarButtonItems.get().start(next: { [weak self] rightBarButtonItems in
|
||||
self?.navigationItem.rightBarButtonItems = rightBarButtonItems
|
||||
}))
|
||||
|
||||
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode in
|
||||
self?.galleryNode.updatePresentationState({
|
||||
$0.withUpdatedFooterContentNode(footerContentNode)
|
||||
@@ -869,6 +877,7 @@ class GalleryController: ViewController {
|
||||
strongSelf.centralItemTitle.set(node.title())
|
||||
strongSelf.centralItemTitleView.set(node.titleView())
|
||||
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
|
||||
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
|
||||
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
|
||||
strongSelf.centralItemFooterContentNode.set(node.footerContent())
|
||||
}
|
||||
@@ -906,6 +915,7 @@ class GalleryController: ViewController {
|
||||
self.centralItemTitle.set(centralItemNode.title())
|
||||
self.centralItemTitleView.set(centralItemNode.titleView())
|
||||
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
|
||||
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
|
||||
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
|
||||
self.centralItemFooterContentNode.set(centralItemNode.footerContent())
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ open class GalleryItemNode: ASDisplayNode {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
open func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
open func footerContent() -> Signal<GalleryFooterContentNode?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import PassKit
|
||||
import Lottie
|
||||
|
||||
private enum ChatMessageGalleryControllerData {
|
||||
case url(String)
|
||||
@@ -125,6 +126,13 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
|
||||
let ext = (fileName as NSString).pathExtension.lowercased()
|
||||
if ext == "wav" || ext == "opus" {
|
||||
return .audio(file)
|
||||
} else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 {
|
||||
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let _ = LOTComposition(filePath: path) {
|
||||
let gallery = GalleryController(context: context, source: .standaloneMessage(message), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
return .gallery(gallery)
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
if ext == "mkv" {
|
||||
|
||||
@@ -131,7 +131,7 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, representatio
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageAnimatedStickerDatas(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<(Data?, Bool), NoError> {
|
||||
func chatMessageAnimationData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<(Data?, Bool), NoError> {
|
||||
let resource = fileReference.media.resource
|
||||
|
||||
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||
|
||||
Reference in New Issue
Block a user