mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Animation and other improvements
This commit is contained in:
parent
0d38a9bd08
commit
bacd88a1ff
@ -12,7 +12,7 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
|
|||||||
public func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
public func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
||||||
let file = fileReference.media
|
let file = fileReference.media
|
||||||
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
|
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
|
||||||
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil)
|
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int64.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
public func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
||||||
@ -37,7 +37,7 @@ public func messageMediaFileInteractiveFetched(context: AccountContext, message:
|
|||||||
return messageMediaFileInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), file: file, userInitiated: userInitiated, priority: .userInitiated)
|
return messageMediaFileInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), file: file, userInitiated: userInitiated, priority: .userInitiated)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: IndexSet = IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), userInitiated: Bool, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: IndexSet = IndexSet(integersIn: 0 ..< Int(Int64.max) as Range<Int>), userInitiated: Bool, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
||||||
let mediaReference = AnyMediaReference.message(message: messageReference, media: file)
|
let mediaReference = AnyMediaReference.message(message: messageReference, media: file)
|
||||||
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: ranges, statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: nil)
|
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: ranges, statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,29 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASTextKitComponents.h>
|
#import <AsyncDisplayKit/ASTextKitComponents.h>
|
||||||
|
#import "ASTextKitContext.h"
|
||||||
#import <AsyncDisplayKit/ASAssert.h>
|
#import <AsyncDisplayKit/ASAssert.h>
|
||||||
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
|
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
|
||||||
|
|
||||||
#import <tgmath.h>
|
#import <tgmath.h>
|
||||||
|
|
||||||
|
@implementation ASCustomTextContainer
|
||||||
|
|
||||||
|
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(nullable CGRect *)remainingRect {
|
||||||
|
CGRect result = [super lineFragmentRectForProposedRect:proposedRect atIndex:characterIndex writingDirection:baseWritingDirection remainingRect:remainingRect];
|
||||||
|
|
||||||
|
/*if (result.origin.y < 10.0f) {
|
||||||
|
result.size.width -= 20.0f;
|
||||||
|
if (result.size.width < 0.0f) {
|
||||||
|
result.size.width = 0.0f;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface ASCustomLayoutManager : NSLayoutManager
|
@interface ASCustomLayoutManager : NSLayoutManager
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -118,7 +136,8 @@
|
|||||||
components.layoutManager = layoutManager;
|
components.layoutManager = layoutManager;
|
||||||
[components.textStorage addLayoutManager:components.layoutManager];
|
[components.textStorage addLayoutManager:components.layoutManager];
|
||||||
|
|
||||||
components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize];
|
components.textContainer = [[ASCustomTextContainer alloc] initWithSize:textContainerSize];
|
||||||
|
//components.textContainer.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(textContainerSize.width - 60.0, 0.0, 60.0, 40.0)]];
|
||||||
components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view.
|
components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view.
|
||||||
[components.layoutManager addTextContainer:components.textContainer];
|
[components.layoutManager addTextContainer:components.textContainer];
|
||||||
|
|
||||||
|
|||||||
@ -50,4 +50,8 @@ AS_SUBCLASSING_RESTRICTED
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface ASCustomTextContainer : NSTextContainer
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
[_textStorage setAttributedString:attributedString];
|
[_textStorage setAttributedString:attributedString];
|
||||||
}
|
}
|
||||||
|
|
||||||
_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
|
_textContainer = [[ASCustomTextContainer alloc] initWithSize:constrainedSize];
|
||||||
// We want the text laid out up to the very edges of the container.
|
// We want the text laid out up to the very edges of the container.
|
||||||
_textContainer.lineFragmentPadding = 0;
|
_textContainer.lineFragmentPadding = 0;
|
||||||
_textContainer.lineBreakMode = lineBreakMode;
|
_textContainer.lineBreakMode = lineBreakMode;
|
||||||
|
|||||||
@ -316,13 +316,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
}
|
}
|
||||||
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
|
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
|
||||||
|
|
||||||
self.emojiViewProvider = { [weak self] emoji in
|
/*self.emojiViewProvider = { [weak self] emoji in
|
||||||
guard let strongSelf = self, let file = strongSelf.context.animatedEmojiStickers[emoji]?.first?.file else {
|
guard let strongSelf = self, let file = strongSelf.context.animatedEmojiStickers[emoji]?.first?.file else {
|
||||||
return UIView()
|
return UIView()
|
||||||
}
|
}
|
||||||
|
|
||||||
return EmojiTextAttachmentView(context: context, file: file)
|
return EmojiTextAttachmentView(context: context, file: file)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
self.updateSendButtonEnabled(isCaption || isAttachment, animated: false)
|
self.updateSendButtonEnabled(isCaption || isAttachment, animated: false)
|
||||||
|
|
||||||
|
|||||||
@ -834,8 +834,12 @@ public final class ReactionNodePool {
|
|||||||
private var views: [ReactionButtonAsyncNode] = []
|
private var views: [ReactionButtonAsyncNode] = []
|
||||||
|
|
||||||
func putBack(view: ReactionButtonAsyncNode) {
|
func putBack(view: ReactionButtonAsyncNode) {
|
||||||
view.reset()
|
assert(view.superview == nil)
|
||||||
self.views.append(view)
|
|
||||||
|
if self.views.count < 64 {
|
||||||
|
view.reset()
|
||||||
|
self.views.append(view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func take() -> Item {
|
func take() -> Item {
|
||||||
@ -892,6 +896,12 @@ public final class ReactionButtonsAsyncLayoutContainer {
|
|||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
for (_, button) in self.buttons {
|
||||||
|
button.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func update(
|
public func update(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
action: @escaping (String) -> Void,
|
action: @escaping (String) -> Void,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import UIKitRuntimeUtils
|
|||||||
}
|
}
|
||||||
if let completion = self.completion {
|
if let completion = self.completion {
|
||||||
completion(flag)
|
completion(flag)
|
||||||
|
self.completion = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +84,9 @@ public extension CALayer {
|
|||||||
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
return animation
|
return animation
|
||||||
} else if timingFunction == kCAMediaTimingFunctionSpring {
|
} else if timingFunction == kCAMediaTimingFunctionSpring {
|
||||||
let animation = makeSpringAnimation(keyPath)
|
let animation = makeSpringAnimation(keyPath)
|
||||||
@ -108,6 +112,10 @@ public extension CALayer {
|
|||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
return animation
|
return animation
|
||||||
} else {
|
} else {
|
||||||
let k = Float(UIView.animationDurationFactor())
|
let k = Float(UIView.animationDurationFactor())
|
||||||
@ -138,6 +146,10 @@ public extension CALayer {
|
|||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,6 +208,10 @@ public extension CALayer {
|
|||||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
self.add(animation, forKey: keyPath)
|
self.add(animation, forKey: keyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +241,10 @@ public extension CALayer {
|
|||||||
animation.speed = speed * Float(animation.duration / duration)
|
animation.speed = speed * Float(animation.duration / duration)
|
||||||
animation.isAdditive = additive
|
animation.isAdditive = additive
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,6 +277,10 @@ public extension CALayer {
|
|||||||
animation.speed = speed * Float(animation.duration / duration)
|
animation.speed = speed * Float(animation.duration / duration)
|
||||||
animation.isAdditive = additive
|
animation.isAdditive = additive
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
self.add(animation, forKey: keyPath)
|
self.add(animation, forKey: keyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +308,10 @@ public extension CALayer {
|
|||||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
self.add(animation, forKey: key)
|
self.add(animation, forKey: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
private final class DisplayLinkTarget: NSObject {
|
public final class DisplayLinkTarget: NSObject {
|
||||||
private let f: () -> Void
|
private let f: () -> Void
|
||||||
|
|
||||||
init(_ f: @escaping () -> Void) {
|
public init(_ f: @escaping () -> Void) {
|
||||||
self.f = f
|
self.f = f
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func event() {
|
@objc public func event() {
|
||||||
self.f()
|
self.f()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -376,6 +376,30 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
|
|
||||||
private let waitingForNodesDisposable = MetaDisposable()
|
private let waitingForNodesDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var auxiliaryDisplayLink: CADisplayLink?
|
||||||
|
private var isAuxiliaryDisplayLinkEnabled: Bool = false {
|
||||||
|
didSet {
|
||||||
|
/*if self.isAuxiliaryDisplayLinkEnabled != oldValue {
|
||||||
|
if self.isAuxiliaryDisplayLinkEnabled {
|
||||||
|
if self.auxiliaryDisplayLink == nil {
|
||||||
|
let displayLink = CADisplayLink(target: DisplayLinkTarget({
|
||||||
|
}), selector: #selector(DisplayLinkTarget.event))
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
displayLink.add(to: RunLoop.main, forMode: .common)
|
||||||
|
self.auxiliaryDisplayLink = displayLink
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let auxiliaryDisplayLink = self.auxiliaryDisplayLink {
|
||||||
|
self.auxiliaryDisplayLink = nil
|
||||||
|
auxiliaryDisplayLink.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*override open var accessibilityElements: [Any]? {
|
/*override open var accessibilityElements: [Any]? {
|
||||||
get {
|
get {
|
||||||
var accessibilityElements: [Any] = []
|
var accessibilityElements: [Any] = []
|
||||||
@ -789,6 +813,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.isDeceleratingAfterTracking = true
|
self.isDeceleratingAfterTracking = true
|
||||||
self.updateHeaderItemsFlashing(animated: true)
|
self.updateHeaderItemsFlashing(animated: true)
|
||||||
self.resetScrollIndicatorFlashTimer(start: false)
|
self.resetScrollIndicatorFlashTimer(start: false)
|
||||||
|
|
||||||
|
self.isAuxiliaryDisplayLinkEnabled = true
|
||||||
} else {
|
} else {
|
||||||
self.isDeceleratingAfterTracking = false
|
self.isDeceleratingAfterTracking = false
|
||||||
self.resetHeaderItemsFlashTimer(start: true)
|
self.resetHeaderItemsFlashTimer(start: true)
|
||||||
@ -797,6 +823,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
|
|
||||||
self.lastContentOffsetTimestamp = 0.0
|
self.lastContentOffsetTimestamp = 0.0
|
||||||
self.didEndScrolling?(false)
|
self.didEndScrolling?(false)
|
||||||
|
self.isAuxiliaryDisplayLinkEnabled = false
|
||||||
}
|
}
|
||||||
self.endedInteractiveDragging(self.touchesPosition)
|
self.endedInteractiveDragging(self.touchesPosition)
|
||||||
}
|
}
|
||||||
@ -807,6 +834,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.resetHeaderItemsFlashTimer(start: true)
|
self.resetHeaderItemsFlashTimer(start: true)
|
||||||
self.updateHeaderItemsFlashing(animated: true)
|
self.updateHeaderItemsFlashing(animated: true)
|
||||||
self.resetScrollIndicatorFlashTimer(start: true)
|
self.resetScrollIndicatorFlashTimer(start: true)
|
||||||
|
self.isAuxiliaryDisplayLinkEnabled = false
|
||||||
if !scrollView.isTracking {
|
if !scrollView.isTracking {
|
||||||
self.didEndScrolling?(true)
|
self.didEndScrolling?(true)
|
||||||
}
|
}
|
||||||
@ -3192,6 +3220,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
springAnimation.isRemovedOnCompletion = true
|
springAnimation.isRemovedOnCompletion = true
|
||||||
springAnimation.isAdditive = true
|
springAnimation.isAdditive = true
|
||||||
springAnimation.fillMode = CAMediaTimingFillMode.forwards
|
springAnimation.fillMode = CAMediaTimingFillMode.forwards
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
springAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
let k = Float(UIView.animationDurationFactor())
|
let k = Float(UIView.animationDurationFactor())
|
||||||
var speed: Float = 1.0
|
var speed: Float = 1.0
|
||||||
@ -3223,6 +3254,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
basicAnimation.isRemovedOnCompletion = true
|
basicAnimation.isRemovedOnCompletion = true
|
||||||
basicAnimation.isAdditive = true
|
basicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
basicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||||
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y)
|
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y)
|
||||||
@ -3231,6 +3265,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
reverseBasicAnimation.isRemovedOnCompletion = true
|
reverseBasicAnimation.isRemovedOnCompletion = true
|
||||||
reverseBasicAnimation.isAdditive = true
|
reverseBasicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
reverseBasicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
animation = basicAnimation
|
animation = basicAnimation
|
||||||
reverseAnimation = reverseBasicAnimation
|
reverseAnimation = reverseBasicAnimation
|
||||||
@ -3245,6 +3282,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
basicAnimation.isRemovedOnCompletion = true
|
basicAnimation.isRemovedOnCompletion = true
|
||||||
basicAnimation.isAdditive = true
|
basicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
basicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||||
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||||
@ -3253,6 +3293,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
reverseBasicAnimation.isRemovedOnCompletion = true
|
reverseBasicAnimation.isRemovedOnCompletion = true
|
||||||
reverseBasicAnimation.isAdditive = true
|
reverseBasicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
reverseBasicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
animation = basicAnimation
|
animation = basicAnimation
|
||||||
reverseAnimation = reverseBasicAnimation
|
reverseAnimation = reverseBasicAnimation
|
||||||
@ -3267,6 +3310,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
basicAnimation.isRemovedOnCompletion = true
|
basicAnimation.isRemovedOnCompletion = true
|
||||||
basicAnimation.isAdditive = true
|
basicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
basicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform")
|
||||||
reverseBasicAnimation.timingFunction = ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction
|
reverseBasicAnimation.timingFunction = ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction
|
||||||
@ -3275,6 +3321,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
|
||||||
reverseBasicAnimation.isRemovedOnCompletion = true
|
reverseBasicAnimation.isRemovedOnCompletion = true
|
||||||
reverseBasicAnimation.isAdditive = true
|
reverseBasicAnimation.isAdditive = true
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
reverseBasicAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
animation = basicAnimation
|
animation = basicAnimation
|
||||||
reverseAnimation = reverseBasicAnimation
|
reverseAnimation = reverseBasicAnimation
|
||||||
|
|||||||
@ -176,17 +176,6 @@ open class ManagedAnimationNode: ASDisplayNode {
|
|||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
|
self.imageNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
|
||||||
|
|
||||||
final class DisplayLinkTarget: NSObject {
|
|
||||||
private let f: () -> Void
|
|
||||||
|
|
||||||
init(_ f: @escaping () -> Void) {
|
|
||||||
self.f = f
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func event() {
|
|
||||||
self.f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var displayLinkUpdate: (() -> Void)?
|
var displayLinkUpdate: (() -> Void)?
|
||||||
self.displayLink = CADisplayLink(target: DisplayLinkTarget {
|
self.displayLink = CADisplayLink(target: DisplayLinkTarget {
|
||||||
displayLinkUpdate?()
|
displayLinkUpdate?()
|
||||||
|
|||||||
@ -21,6 +21,38 @@ private func decodeStickerThumbnailData(_ data: Data) -> String {
|
|||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func generateStickerPlaceholderImage(data: Data?, size: CGSize, imageSize: CGSize, backgroundColor: UIColor?, foregroundColor: UIColor) -> UIImage? {
|
||||||
|
return generateImage(size, rotatedContext: { size, context in
|
||||||
|
if let backgroundColor = backgroundColor {
|
||||||
|
context.setFillColor(backgroundColor.cgColor)
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
} else {
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(foregroundColor.cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = data {
|
||||||
|
var path = decodeStickerThumbnailData(data)
|
||||||
|
if !path.hasSuffix("z") {
|
||||||
|
path = "\(path)z"
|
||||||
|
}
|
||||||
|
let reader = PathDataReader(input: path)
|
||||||
|
let segments = reader.read()
|
||||||
|
|
||||||
|
let scale = max(size.width, size.height) / max(imageSize.width, imageSize.height)
|
||||||
|
context.scaleBy(x: scale, y: scale)
|
||||||
|
renderPath(segments, context: context)
|
||||||
|
} else {
|
||||||
|
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10.0, height: 10.0))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
path.fill()
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public class StickerShimmerEffectNode: ASDisplayNode {
|
public class StickerShimmerEffectNode: ASDisplayNode {
|
||||||
private var backdropNode: ASDisplayNode?
|
private var backdropNode: ASDisplayNode?
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
@ -84,35 +116,7 @@ public class StickerShimmerEffectNode: ASDisplayNode {
|
|||||||
self.effectNode.update(backgroundColor: backgroundColor == nil ? .clear : foregroundColor, foregroundColor: shimmeringColor, horizontal: true, effectSize: nil, globalTimeOffset: true, duration: nil)
|
self.effectNode.update(backgroundColor: backgroundColor == nil ? .clear : foregroundColor, foregroundColor: shimmeringColor, horizontal: true, effectSize: nil, globalTimeOffset: true, duration: nil)
|
||||||
|
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
let image = generateImage(size, rotatedContext: { size, context in
|
let image = generateStickerPlaceholderImage(data: data, size: size, imageSize: imageSize, backgroundColor: backgroundColor, foregroundColor: .black)
|
||||||
if let backgroundColor = backgroundColor {
|
|
||||||
context.setFillColor(backgroundColor.cgColor)
|
|
||||||
context.setBlendMode(.copy)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
|
||||||
} else {
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
context.setFillColor(UIColor.black.cgColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let data = data {
|
|
||||||
var path = decodeStickerThumbnailData(data)
|
|
||||||
if !path.hasSuffix("z") {
|
|
||||||
path = "\(path)z"
|
|
||||||
}
|
|
||||||
let reader = PathDataReader(input: path)
|
|
||||||
let segments = reader.read()
|
|
||||||
|
|
||||||
let scale = max(size.width, size.height) / max(imageSize.width, imageSize.height)
|
|
||||||
context.scaleBy(x: scale, y: scale)
|
|
||||||
renderPath(segments, context: context)
|
|
||||||
} else {
|
|
||||||
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10.0, height: 10.0))
|
|
||||||
UIGraphicsPushContext(context)
|
|
||||||
path.fill()
|
|
||||||
UIGraphicsPopContext()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if backgroundColor == nil {
|
if backgroundColor == nil {
|
||||||
self.foregroundNode.image = nil
|
self.foregroundNode.image = nil
|
||||||
|
|||||||
@ -360,7 +360,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1678949555] = { return Api.InputWebDocument.parse_inputWebDocument($0) }
|
dict[-1678949555] = { return Api.InputWebDocument.parse_inputWebDocument($0) }
|
||||||
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
||||||
dict[-1036396922] = { return Api.InputWebFileLocation.parse_inputWebFileLocation($0) }
|
dict[-1036396922] = { return Api.InputWebFileLocation.parse_inputWebFileLocation($0) }
|
||||||
dict[215516896] = { return Api.Invoice.parse_invoice($0) }
|
dict[-1197014651] = { return Api.Invoice.parse_invoice($0) }
|
||||||
dict[-1059185703] = { return Api.JSONObjectValue.parse_jsonObjectValue($0) }
|
dict[-1059185703] = { return Api.JSONObjectValue.parse_jsonObjectValue($0) }
|
||||||
dict[-146520221] = { return Api.JSONValue.parse_jsonArray($0) }
|
dict[-146520221] = { return Api.JSONValue.parse_jsonArray($0) }
|
||||||
dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) }
|
dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) }
|
||||||
@ -808,6 +808,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) }
|
dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) }
|
||||||
dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) }
|
dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) }
|
||||||
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
|
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
|
||||||
|
dict[-2006880112] = { return Api.Update.parse_updateTranscribeAudio($0) }
|
||||||
dict[-1007549728] = { return Api.Update.parse_updateUserName($0) }
|
dict[-1007549728] = { return Api.Update.parse_updateUserName($0) }
|
||||||
dict[88680979] = { return Api.Update.parse_updateUserPhone($0) }
|
dict[88680979] = { return Api.Update.parse_updateUserPhone($0) }
|
||||||
dict[-232290676] = { return Api.Update.parse_updateUserPhoto($0) }
|
dict[-232290676] = { return Api.Update.parse_updateUserPhoto($0) }
|
||||||
@ -987,7 +988,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) }
|
dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) }
|
||||||
dict[816245886] = { return Api.messages.Stickers.parse_stickers($0) }
|
dict[816245886] = { return Api.messages.Stickers.parse_stickers($0) }
|
||||||
dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) }
|
dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) }
|
||||||
dict[-1077051894] = { return Api.messages.TranscribedAudio.parse_transcribedAudio($0) }
|
dict[-1821037486] = { return Api.messages.TranscribedAudio.parse_transcribedAudio($0) }
|
||||||
dict[1741309751] = { return Api.messages.TranslatedText.parse_translateNoResult($0) }
|
dict[1741309751] = { return Api.messages.TranslatedText.parse_translateNoResult($0) }
|
||||||
dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
|
dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
|
||||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||||
|
|||||||
@ -599,6 +599,7 @@ public extension Api {
|
|||||||
case updateStickerSets
|
case updateStickerSets
|
||||||
case updateStickerSetsOrder(flags: Int32, order: [Int64])
|
case updateStickerSetsOrder(flags: Int32, order: [Int64])
|
||||||
case updateTheme(theme: Api.Theme)
|
case updateTheme(theme: Api.Theme)
|
||||||
|
case updateTranscribeAudio(flags: Int32, transcriptionId: Int64, text: String)
|
||||||
case updateUserName(userId: Int64, firstName: String, lastName: String, username: String)
|
case updateUserName(userId: Int64, firstName: String, lastName: String, username: String)
|
||||||
case updateUserPhone(userId: Int64, phone: String)
|
case updateUserPhone(userId: Int64, phone: String)
|
||||||
case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool)
|
case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool)
|
||||||
@ -1423,6 +1424,14 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
theme.serialize(buffer, true)
|
theme.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
|
case .updateTranscribeAudio(let flags, let transcriptionId, let text):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-2006880112)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
serializeInt64(transcriptionId, buffer: buffer, boxed: false)
|
||||||
|
serializeString(text, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
case .updateUserName(let userId, let firstName, let lastName, let username):
|
case .updateUserName(let userId, let firstName, let lastName, let username):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1007549728)
|
buffer.appendInt32(-1007549728)
|
||||||
@ -1667,6 +1676,8 @@ public extension Api {
|
|||||||
return ("updateStickerSetsOrder", [("flags", String(describing: flags)), ("order", String(describing: order))])
|
return ("updateStickerSetsOrder", [("flags", String(describing: flags)), ("order", String(describing: order))])
|
||||||
case .updateTheme(let theme):
|
case .updateTheme(let theme):
|
||||||
return ("updateTheme", [("theme", String(describing: theme))])
|
return ("updateTheme", [("theme", String(describing: theme))])
|
||||||
|
case .updateTranscribeAudio(let flags, let transcriptionId, let text):
|
||||||
|
return ("updateTranscribeAudio", [("flags", String(describing: flags)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
|
||||||
case .updateUserName(let userId, let firstName, let lastName, let username):
|
case .updateUserName(let userId, let firstName, let lastName, let username):
|
||||||
return ("updateUserName", [("userId", String(describing: userId)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("username", String(describing: username))])
|
return ("updateUserName", [("userId", String(describing: userId)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("username", String(describing: username))])
|
||||||
case .updateUserPhone(let userId, let phone):
|
case .updateUserPhone(let userId, let phone):
|
||||||
@ -3325,6 +3336,23 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_updateTranscribeAudio(_ reader: BufferReader) -> Update? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Int64?
|
||||||
|
_2 = reader.readInt64()
|
||||||
|
var _3: String?
|
||||||
|
_3 = parseString(reader)
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.Update.updateTranscribeAudio(flags: _1!, transcriptionId: _2!, text: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_updateUserName(_ reader: BufferReader) -> Update? {
|
public static func parse_updateUserName(_ reader: BufferReader) -> Update? {
|
||||||
var _1: Int64?
|
var _1: Int64?
|
||||||
_1 = reader.readInt64()
|
_1 = reader.readInt64()
|
||||||
|
|||||||
@ -490,14 +490,15 @@ public extension Api.messages {
|
|||||||
}
|
}
|
||||||
public extension Api.messages {
|
public extension Api.messages {
|
||||||
enum TranscribedAudio: TypeConstructorDescription {
|
enum TranscribedAudio: TypeConstructorDescription {
|
||||||
case transcribedAudio(transcriptionId: Int64, text: String)
|
case transcribedAudio(flags: Int32, transcriptionId: Int64, text: String)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .transcribedAudio(let transcriptionId, let text):
|
case .transcribedAudio(let flags, let transcriptionId, let text):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1077051894)
|
buffer.appendInt32(-1821037486)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(transcriptionId, buffer: buffer, boxed: false)
|
serializeInt64(transcriptionId, buffer: buffer, boxed: false)
|
||||||
serializeString(text, buffer: buffer, boxed: false)
|
serializeString(text, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
@ -506,20 +507,23 @@ public extension Api.messages {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .transcribedAudio(let transcriptionId, let text):
|
case .transcribedAudio(let flags, let transcriptionId, let text):
|
||||||
return ("transcribedAudio", [("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
|
return ("transcribedAudio", [("flags", String(describing: flags)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? {
|
public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? {
|
||||||
var _1: Int64?
|
var _1: Int32?
|
||||||
_1 = reader.readInt64()
|
_1 = reader.readInt32()
|
||||||
var _2: String?
|
var _2: Int64?
|
||||||
_2 = parseString(reader)
|
_2 = reader.readInt64()
|
||||||
|
var _3: String?
|
||||||
|
_3 = parseString(reader)
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
if _c1 && _c2 {
|
let _c3 = _3 != nil
|
||||||
return Api.messages.TranscribedAudio.transcribedAudio(transcriptionId: _1!, text: _2!)
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -6226,6 +6226,21 @@ public extension Api.functions.payments {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.payments {
|
||||||
|
static func assignPlayMarketTransaction(purchaseToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1336560365)
|
||||||
|
serializeString(purchaseToken, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "payments.assignPlayMarketTransaction", parameters: [("purchaseToken", String(describing: purchaseToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -6319,6 +6334,23 @@ public extension Api.functions.payments {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.payments {
|
||||||
|
static func requestRecurrentPayment(userId: Int64, recurrentInitCharge: String, invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1329030023)
|
||||||
|
serializeInt64(userId, buffer: buffer, boxed: false)
|
||||||
|
serializeString(recurrentInitCharge, buffer: buffer, boxed: false)
|
||||||
|
invoiceMedia.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "payments.requestRecurrentPayment", parameters: [("userId", String(describing: userId)), ("recurrentInitCharge", String(describing: recurrentInitCharge)), ("invoiceMedia", String(describing: invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
|
static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
|||||||
@ -898,13 +898,13 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum Invoice: TypeConstructorDescription {
|
enum Invoice: TypeConstructorDescription {
|
||||||
case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?)
|
case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, recurrentTermsUrl: String?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts):
|
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let recurrentTermsUrl):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(215516896)
|
buffer.appendInt32(-1197014651)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(currency, buffer: buffer, boxed: false)
|
serializeString(currency, buffer: buffer, boxed: false)
|
||||||
@ -919,14 +919,15 @@ public extension Api {
|
|||||||
for item in suggestedTipAmounts! {
|
for item in suggestedTipAmounts! {
|
||||||
serializeInt64(item, buffer: buffer, boxed: false)
|
serializeInt64(item, buffer: buffer, boxed: false)
|
||||||
}}
|
}}
|
||||||
|
if Int(flags) & Int(1 << 8) != 0 {serializeString(recurrentTermsUrl!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts):
|
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let recurrentTermsUrl):
|
||||||
return ("invoice", [("flags", String(describing: flags)), ("currency", String(describing: currency)), ("prices", String(describing: prices)), ("maxTipAmount", String(describing: maxTipAmount)), ("suggestedTipAmounts", String(describing: suggestedTipAmounts))])
|
return ("invoice", [("flags", String(describing: flags)), ("currency", String(describing: currency)), ("prices", String(describing: prices)), ("maxTipAmount", String(describing: maxTipAmount)), ("suggestedTipAmounts", String(describing: suggestedTipAmounts)), ("recurrentTermsUrl", String(describing: recurrentTermsUrl))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -945,13 +946,16 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 8) != 0 {if let _ = reader.readInt32() {
|
if Int(_1!) & Int(1 << 8) != 0 {if let _ = reader.readInt32() {
|
||||||
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
|
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
|
||||||
} }
|
} }
|
||||||
|
var _6: String?
|
||||||
|
if Int(_1!) & Int(1 << 8) != 0 {_6 = parseString(reader) }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil
|
||||||
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = (Int(_1!) & Int(1 << 8) == 0) || _6 != nil
|
||||||
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||||
|
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, recurrentTermsUrl: _6)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1101,6 +1101,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*case let .updateTranscribeAudio(flags, transcriptionId, text):
|
||||||
|
break*/
|
||||||
case let .updateNotifySettings(apiPeer, apiNotificationSettings):
|
case let .updateNotifySettings(apiPeer, apiNotificationSettings):
|
||||||
switch apiPeer {
|
switch apiPeer {
|
||||||
case let .notifyPeer(peer):
|
case let .notifyPeer(peer):
|
||||||
|
|||||||
@ -3,24 +3,28 @@ import Postbox
|
|||||||
public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
|
public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
|
||||||
public let id: Int64
|
public let id: Int64
|
||||||
public let text: String
|
public let text: String
|
||||||
|
public let isPending: Bool
|
||||||
|
|
||||||
public var associatedPeerIds: [PeerId] {
|
public var associatedPeerIds: [PeerId] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(id: Int64, text: String) {
|
public init(id: Int64, text: String, isPending: Bool) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.isPending = isPending
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(decoder: PostboxDecoder) {
|
required public init(decoder: PostboxDecoder) {
|
||||||
self.id = decoder.decodeInt64ForKey("id", orElse: 0)
|
self.id = decoder.decodeInt64ForKey("id", orElse: 0)
|
||||||
self.text = decoder.decodeStringForKey("text", orElse: "")
|
self.text = decoder.decodeStringForKey("text", orElse: "")
|
||||||
|
self.isPending = decoder.decodeBoolForKey("isPending", orElse: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeInt64(self.id, forKey: "id")
|
encoder.encodeInt64(self.id, forKey: "id")
|
||||||
encoder.encodeString(self.text, forKey: "text")
|
encoder.encodeString(self.text, forKey: "text")
|
||||||
|
encoder.encodeBool(self.isPending, forKey: "isPending")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: AudioTranscriptionMessageAttribute, rhs: AudioTranscriptionMessageAttribute) -> Bool {
|
public static func ==(lhs: AudioTranscriptionMessageAttribute, rhs: AudioTranscriptionMessageAttribute) -> Bool {
|
||||||
@ -30,6 +34,9 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
|
|||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isPending != rhs.isPending {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,12 +64,14 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
|
|||||||
|
|
||||||
return postbox.transaction { transaction -> EngineAudioTranscriptionResult in
|
return postbox.transaction { transaction -> EngineAudioTranscriptionResult in
|
||||||
switch result {
|
switch result {
|
||||||
case let .transcribedAudio(transcriptionId, text):
|
case let .transcribedAudio(flags, transcriptionId, text):
|
||||||
transaction.updateMessage(messageId, update: { currentMessage in
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
|
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
|
||||||
|
|
||||||
attributes.append(AudioTranscriptionMessageAttribute(id: transcriptionId, text: text))
|
let isPending = (flags & (1 << 0)) != 0
|
||||||
|
|
||||||
|
attributes.append(AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending))
|
||||||
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
})
|
})
|
||||||
|
|||||||
@ -124,7 +124,7 @@ public enum BotPaymentFormRequestError {
|
|||||||
extension BotPaymentInvoice {
|
extension BotPaymentInvoice {
|
||||||
init(apiInvoice: Api.Invoice) {
|
init(apiInvoice: Api.Invoice) {
|
||||||
switch apiInvoice {
|
switch apiInvoice {
|
||||||
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts):
|
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, _):
|
||||||
var fields = BotPaymentInvoiceFields()
|
var fields = BotPaymentInvoiceFields()
|
||||||
if (flags & (1 << 1)) != 0 {
|
if (flags & (1 << 1)) != 0 {
|
||||||
fields.insert(.name)
|
fields.insert(.name)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ public protocol AnimationCacheItemWriter: AnyObject {
|
|||||||
|
|
||||||
public protocol AnimationCache: AnyObject {
|
public protocol AnimationCache: AnyObject {
|
||||||
func get(sourceId: String, fetch: @escaping (AnimationCacheItemWriter) -> Disposable) -> Signal<AnimationCacheItem?, NoError>
|
func get(sourceId: String, fetch: @escaping (AnimationCacheItemWriter) -> Disposable) -> Signal<AnimationCacheItem?, NoError>
|
||||||
|
func getSynchronously(sourceId: String) -> AnimationCacheItem?
|
||||||
}
|
}
|
||||||
|
|
||||||
private func md5Hash(_ string: String) -> String {
|
private func md5Hash(_ string: String) -> String {
|
||||||
@ -375,6 +376,18 @@ public final class AnimationCacheImpl: AnimationCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSynchronously(sourceId: String) -> AnimationCacheItem? {
|
||||||
|
let sourceIdPath = itemSubpath(hashString: md5Hash(sourceId))
|
||||||
|
let itemDirectoryPath = "\(self.basePath)/\(sourceIdPath.directory)"
|
||||||
|
let itemPath = "\(itemDirectoryPath)/\(sourceIdPath.fileName)"
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: itemPath) {
|
||||||
|
return loadItem(path: itemPath)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
@ -403,4 +416,10 @@ public final class AnimationCacheImpl: AnimationCache {
|
|||||||
}
|
}
|
||||||
|> runOn(self.queue)
|
|> runOn(self.queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getSynchronously(sourceId: String) -> AnimationCacheItem? {
|
||||||
|
return self.impl.syncWith { impl -> AnimationCacheItem? in
|
||||||
|
return impl.getSynchronously(sourceId: sourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import LottieAnimationComponent
|
|||||||
|
|
||||||
public final class AudioTranscriptionButtonComponent: Component {
|
public final class AudioTranscriptionButtonComponent: Component {
|
||||||
public enum TranscriptionState {
|
public enum TranscriptionState {
|
||||||
case possible
|
|
||||||
case inProgress
|
case inProgress
|
||||||
case expanded
|
case expanded
|
||||||
case collapsed
|
case collapsed
|
||||||
@ -130,8 +129,6 @@ public final class AudioTranscriptionButtonComponent: Component {
|
|||||||
|
|
||||||
let animationName: String
|
let animationName: String
|
||||||
switch component.transcriptionState {
|
switch component.transcriptionState {
|
||||||
case .possible:
|
|
||||||
animationName = "voiceToText"
|
|
||||||
case .inProgress:
|
case .inProgress:
|
||||||
animationName = "voiceToText"
|
animationName = "voiceToText"
|
||||||
case .collapsed:
|
case .collapsed:
|
||||||
|
|||||||
@ -19,6 +19,10 @@ swift_library(
|
|||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
"//submodules/YuvConversion:YuvConversion",
|
"//submodules/YuvConversion:YuvConversion",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||||
|
"//submodules/TelegramUI/Components/LottieAnimationCache:LottieAnimationCache",
|
||||||
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||||
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -9,103 +9,79 @@ import AccountContext
|
|||||||
import YuvConversion
|
import YuvConversion
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Postbox
|
import Postbox
|
||||||
|
import AnimationCache
|
||||||
|
import LottieAnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
private final class InlineStickerItemLayer: SimpleLayer {
|
public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||||
static let queue = Queue()
|
public static let queue = Queue()
|
||||||
|
|
||||||
|
public struct Key: Hashable {
|
||||||
|
public var id: MediaId
|
||||||
|
public var index: Int
|
||||||
|
|
||||||
|
public init(id: MediaId, index: Int) {
|
||||||
|
self.id = id
|
||||||
|
self.index = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let file: TelegramMediaFile
|
private let file: TelegramMediaFile
|
||||||
private let source: AnimatedStickerNodeSource
|
|
||||||
private var frameSource: QueueLocalObject<AnimatedStickerDirectFrameSource>?
|
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
private var fetchDisposable: Disposable?
|
private var fetchDisposable: Disposable?
|
||||||
|
|
||||||
private var isInHierarchyValue: Bool = false
|
private var isInHierarchyValue: Bool = false
|
||||||
var isVisibleForAnimations: Bool = false {
|
public var isVisibleForAnimations: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
self.updatePlayback()
|
if self.isVisibleForAnimations != oldValue {
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var displayLink: ConstantDisplayLinkAnimator?
|
private var displayLink: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
init(context: AccountContext, file: TelegramMediaFile) {
|
public init(context: AccountContext, groupId: String, attemptSynchronousLoad: Bool, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
|
||||||
self.source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
|
||||||
self.file = file
|
self.file = file
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
if attemptSynchronousLoad {
|
||||||
let width = Int(24 * UIScreenScale)
|
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation) {
|
||||||
let height = Int(24 * UIScreenScale)
|
let size = CGSize(width: 24.0, height: 24.0)
|
||||||
|
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) {
|
||||||
let directDataPath = Atomic<String?>(value: nil)
|
|
||||||
let _ = (self.source.directDataPath(attemptSynchronously: true) |> take(1)).start(next: { result in
|
|
||||||
let _ = directDataPath.swap(result)
|
|
||||||
})
|
|
||||||
|
|
||||||
if let directDataPath = directDataPath.with({ $0 }), let directData = try? Data(contentsOf: URL(fileURLWithPath: directDataPath), options: .alwaysMapped) {
|
|
||||||
let syncFrameSource = AnimatedStickerDirectFrameSource(queue: .mainQueue(), data: directData, width: width, height: height, cachePathPrefix: pathPrefix, useMetalCache: false, fitzModifier: nil)!
|
|
||||||
|
|
||||||
if let animationFrame = syncFrameSource.takeFrame(draw: true) {
|
|
||||||
var image: UIImage?
|
|
||||||
|
|
||||||
autoreleasepool {
|
|
||||||
image = generateImagePixel(CGSize(width: CGFloat(animationFrame.width), height: CGFloat(animationFrame.height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
|
||||||
var data = animationFrame.data
|
|
||||||
data.withUnsafeMutableBytes { bytes -> Void in
|
|
||||||
guard let baseAddress = bytes.baseAddress else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch animationFrame.type {
|
|
||||||
case .argb:
|
|
||||||
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
|
||||||
case .yuva:
|
|
||||||
if animationFrame.bytesPerRow <= 0 || animationFrame.height <= 0 || animationFrame.width <= 0 || animationFrame.bytesPerRow * animationFrame.height > bytes.count {
|
|
||||||
assert(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(animationFrame.width), Int32(animationFrame.height), Int32(contextBytesPerRow))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let image = image {
|
|
||||||
self.contents = image.cgImage
|
self.contents = image.cgImage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposable = (self.source.directDataPath(attemptSynchronously: false)
|
self.disposable = renderer.add(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, fetch: { writer in
|
||||||
|> filter { $0 != nil }
|
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||||
|> take(1)
|
|
||||||
|> deliverOn(InlineStickerItemLayer.queue)).start(next: { [weak self] path in
|
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||||
guard let directData = try? Data(contentsOf: URL(fileURLWithPath: path!), options: [.mappedRead]) else {
|
guard let result = result else {
|
||||||
return
|
|
||||||
}
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.frameSource = QueueLocalObject(queue: InlineStickerItemLayer.queue, generate: {
|
|
||||||
return AnimatedStickerDirectFrameSource(queue: InlineStickerItemLayer.queue, data: directData, width: width, height: height, cachePathPrefix: pathPrefix, useMetalCache: false, fitzModifier: nil)!
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||||
})
|
writer.finish()
|
||||||
strongSelf.updatePlayback()
|
return
|
||||||
|
}
|
||||||
|
let scale = min(2.0, UIScreenScale)
|
||||||
|
cacheLottieAnimation(data: data, width: Int(24 * scale), height: Int(24 * scale), writer: writer)
|
||||||
|
})
|
||||||
|
|
||||||
|
let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
dataDisposable.dispose()
|
||||||
|
fetchDisposable.dispose()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(layer: Any) {
|
override public init(layer: Any) {
|
||||||
guard let layer = layer as? InlineStickerItemLayer else {
|
preconditionFailure()
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
self.source = layer.source
|
|
||||||
self.file = layer.file
|
|
||||||
|
|
||||||
super.init(layer: layer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
required public init?(coder: NSCoder) {
|
||||||
@ -117,7 +93,7 @@ private final class InlineStickerItemLayer: SimpleLayer {
|
|||||||
self.fetchDisposable?.dispose()
|
self.fetchDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func action(forKey event: String) -> CAAction? {
|
override public func action(forKey event: String) -> CAAction? {
|
||||||
if event == kCAOnOrderIn {
|
if event == kCAOnOrderIn {
|
||||||
self.isInHierarchyValue = true
|
self.isInHierarchyValue = true
|
||||||
} else if event == kCAOnOrderOut {
|
} else if event == kCAOnOrderOut {
|
||||||
@ -128,72 +104,17 @@ private final class InlineStickerItemLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updatePlayback() {
|
private func updatePlayback() {
|
||||||
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations && self.frameSource != nil
|
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations
|
||||||
if shouldBePlaying != (self.displayLink != nil) {
|
|
||||||
if shouldBePlaying {
|
self.shouldBeAnimating = shouldBePlaying
|
||||||
self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
|
|
||||||
self?.loadNextFrame()
|
|
||||||
})
|
|
||||||
self.displayLink?.isPaused = false
|
|
||||||
} else {
|
|
||||||
self.displayLink?.invalidate()
|
|
||||||
self.displayLink = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var didRequestFrame = false
|
|
||||||
|
|
||||||
private func loadNextFrame() {
|
|
||||||
guard let frameSource = self.frameSource else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.didRequestFrame = true
|
|
||||||
frameSource.with { [weak self] impl in
|
|
||||||
if let animationFrame = impl.takeFrame(draw: true) {
|
|
||||||
var image: UIImage?
|
|
||||||
|
|
||||||
autoreleasepool {
|
|
||||||
image = generateImagePixel(CGSize(width: CGFloat(animationFrame.width), height: CGFloat(animationFrame.height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
|
||||||
var data = animationFrame.data
|
|
||||||
data.withUnsafeMutableBytes { bytes -> Void in
|
|
||||||
guard let baseAddress = bytes.baseAddress else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch animationFrame.type {
|
|
||||||
case .argb:
|
|
||||||
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
|
||||||
case .yuva:
|
|
||||||
if animationFrame.bytesPerRow <= 0 || animationFrame.height <= 0 || animationFrame.width <= 0 || animationFrame.bytesPerRow * animationFrame.height > bytes.count {
|
|
||||||
assert(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(animationFrame.width), Int32(animationFrame.height), Int32(contextBytesPerRow))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let image = image {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.contents = image.cgImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class EmojiTextAttachmentView: UIView {
|
public final class EmojiTextAttachmentView: UIView {
|
||||||
private let contentLayer: InlineStickerItemLayer
|
private let contentLayer: InlineStickerItemLayer
|
||||||
|
|
||||||
public init(context: AccountContext, file: TelegramMediaFile) {
|
public init(context: AccountContext, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
|
||||||
self.contentLayer = InlineStickerItemLayer(context: context, file: file)
|
self.contentLayer = InlineStickerItemLayer(context: context, groupId: "textInputView", attemptSynchronousLoad: true, file: file, cache: cache, renderer: renderer, placeholderColor: placeholderColor)
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AnimationCache
|
|||||||
|
|
||||||
public protocol MultiAnimationRenderer: AnyObject {
|
public protocol MultiAnimationRenderer: AnyObject {
|
||||||
func add(groupId: String, target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, fetch: @escaping (AnimationCacheItemWriter) -> Disposable) -> Disposable
|
func add(groupId: String, target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, fetch: @escaping (AnimationCacheItemWriter) -> Disposable) -> Disposable
|
||||||
|
func loadFirstFrameSynchronously(groupId: String, target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
open class MultiAnimationRenderTarget: SimpleLayer {
|
open class MultiAnimationRenderTarget: SimpleLayer {
|
||||||
@ -159,6 +160,19 @@ private final class ItemAnimationContext {
|
|||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAddedTarget(target: MultiAnimationRenderTarget) {
|
||||||
|
if let item = self.item, let currentFrameGroup = self.currentFrameGroup {
|
||||||
|
let currentFrame = self.frameIndex % item.numFrames
|
||||||
|
|
||||||
|
if let contentsRect = currentFrameGroup.contentsRect(index: currentFrame) {
|
||||||
|
target.contents = currentFrameGroup.image.cgImage
|
||||||
|
target.contentsRect = contentsRect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateIsPlaying()
|
||||||
|
}
|
||||||
|
|
||||||
func updateIsPlaying() {
|
func updateIsPlaying() {
|
||||||
var isPlaying = true
|
var isPlaying = true
|
||||||
if self.item == nil {
|
if self.item == nil {
|
||||||
@ -268,6 +282,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let index = itemContext.targets.add(Weak(target))
|
let index = itemContext.targets.add(Weak(target))
|
||||||
|
itemContext.updateAddedTarget(target: target)
|
||||||
|
|
||||||
let deinitIndex = target.deinitCallbacks.add { [weak self, weak itemContext] in
|
let deinitIndex = target.deinitCallbacks.add { [weak self, weak itemContext] in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -303,6 +318,20 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String) -> Bool {
|
||||||
|
if let item = cache.getSynchronously(sourceId: itemId) {
|
||||||
|
guard let frameGroup = FrameGroup(item: item, baseFrameIndex: 0, count: 1, skip: 1) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target.contents = frameGroup.image.cgImage
|
||||||
|
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateIsPlaying() {
|
private func updateIsPlaying() {
|
||||||
var isPlaying = false
|
var isPlaying = false
|
||||||
for (_, itemContext) in self.itemContexts {
|
for (_, itemContext) in self.itemContexts {
|
||||||
@ -380,6 +409,23 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func loadFirstFrameSynchronously(groupId: String, target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String) -> Bool {
|
||||||
|
let groupContext: GroupContext
|
||||||
|
if let current = self.groupContexts[groupId] {
|
||||||
|
groupContext = current
|
||||||
|
} else {
|
||||||
|
groupContext = GroupContext(stateUpdated: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updateIsPlaying()
|
||||||
|
})
|
||||||
|
self.groupContexts[groupId] = groupContext
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupContext.loadFirstFrameSynchronously(target: target, cache: cache, itemId: itemId)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateIsPlaying() {
|
private func updateIsPlaying() {
|
||||||
var isPlaying = false
|
var isPlaying = false
|
||||||
for (_, groupContext) in self.groupContexts {
|
for (_, groupContext) in self.groupContexts {
|
||||||
|
|||||||
@ -972,7 +972,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let _ = combineLatest(queue: .mainQueue(),
|
let _ = combineLatest(queue: .mainQueue(),
|
||||||
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)),
|
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)),
|
||||||
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction),
|
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction, messageNode: node as? ChatMessageItemView),
|
||||||
strongSelf.context.engine.stickers.availableReactions(),
|
strongSelf.context.engine.stickers.availableReactions(),
|
||||||
peerAllowedReactions(context: strongSelf.context, peerId: topMessage.id.peerId),
|
peerAllowedReactions(context: strongSelf.context, peerId: topMessage.id.peerId),
|
||||||
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
|
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||||
|
|||||||
@ -2473,7 +2473,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
attributes.append(ForwardOptionsMessageAttribute(hideNames: self.chatPresentationInterfaceState.interfaceState.forwardOptionsState?.hideNames == true, hideCaptions: self.chatPresentationInterfaceState.interfaceState.forwardOptionsState?.hideCaptions == true))
|
attributes.append(ForwardOptionsMessageAttribute(hideNames: self.chatPresentationInterfaceState.interfaceState.forwardOptionsState?.hideNames == true, hideCaptions: self.chatPresentationInterfaceState.interfaceState.forwardOptionsState?.hideCaptions == true))
|
||||||
|
|
||||||
for id in forwardMessageIds {
|
for id in forwardMessageIds.sorted() {
|
||||||
messages.append(.forward(source: id, grouping: .auto, attributes: attributes, correlationId: nil))
|
messages.append(.forward(source: id, grouping: .auto, attributes: attributes, correlationId: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -294,14 +294,16 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 && chatPresentationInterfaceState.interfaceState.forwardMessageIds == nil {
|
let isTextEmpty = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0
|
||||||
if chatPresentationInterfaceState.hasScheduledMessages {
|
|
||||||
|
if chatPresentationInterfaceState.interfaceState.forwardMessageIds == nil {
|
||||||
|
if isTextEmpty && chatPresentationInterfaceState.hasScheduledMessages {
|
||||||
accessoryItems.append(.scheduledMessages)
|
accessoryItems.append(.scheduledMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stickersEnabled = true
|
var stickersEnabled = true
|
||||||
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
if case .broadcast = peer.info, canSendMessagesToPeer(peer) {
|
if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) {
|
||||||
accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting))
|
accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting))
|
||||||
}
|
}
|
||||||
if peer.hasBannedPermission(.banSendStickers) != nil {
|
if peer.hasBannedPermission(.banSendStickers) != nil {
|
||||||
@ -312,11 +314,17 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
|||||||
stickersEnabled = false
|
stickersEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands {
|
if isTextEmpty && chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands {
|
||||||
accessoryItems.append(.commands)
|
accessoryItems.append(.commands)
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
accessoryItems.append(.stickers(stickersEnabled))
|
accessoryItems.append(.stickers(stickersEnabled))
|
||||||
if let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
|
#else
|
||||||
|
if isTextEmpty {
|
||||||
|
accessoryItems.append(.stickers(stickersEnabled))
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id {
|
||||||
accessoryItems.append(.inputButtons)
|
accessoryItems.append(.inputButtons)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -374,10 +374,15 @@ func updatedChatEditInterfaceMessageState(state: ChatPresentationInterfaceState,
|
|||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?, readStats: MessageReadStats? = nil) -> Signal<ContextController.Items, NoError> {
|
func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?, readStats: MessageReadStats? = nil, messageNode: ChatMessageItemView? = nil) -> Signal<ContextController.Items, NoError> {
|
||||||
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
|
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
|
||||||
return .single(ContextController.Items(content: .list([])))
|
return .single(ContextController.Items(content: .list([])))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasExpandedAudioTranscription = false
|
||||||
|
if let messageNode = messageNode as? ChatMessageBubbleItemNode {
|
||||||
|
hasExpandedAudioTranscription = messageNode.hasExpandedAudioTranscription()
|
||||||
|
}
|
||||||
|
|
||||||
if messages.count == 1, let _ = messages[0].adAttribute {
|
if messages.count == 1, let _ = messages[0].adAttribute {
|
||||||
let message = messages[0]
|
let message = messages[0]
|
||||||
@ -704,7 +709,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hasRateTranscription = false
|
var hasRateTranscription = false
|
||||||
if let audioTranscription = audioTranscription {
|
if hasExpandedAudioTranscription, let audioTranscription = audioTranscription {
|
||||||
hasRateTranscription = true
|
hasRateTranscription = true
|
||||||
actions.insert(.custom(ChatRateTranscriptionContextItem(context: context, message: message, action: { [weak context] value in
|
actions.insert(.custom(ChatRateTranscriptionContextItem(context: context, message: message, action: { [weak context] value in
|
||||||
guard let context = context else {
|
guard let context = context else {
|
||||||
@ -812,7 +817,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
|
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
if hasExpandedAudioTranscription, let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
||||||
if !messageText.isEmpty {
|
if !messageText.isEmpty {
|
||||||
messageText.append("\n")
|
messageText.append("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4026,4 +4026,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasExpandedAudioTranscription() -> Bool {
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
if let contentNode = contentNode as? ChatMessageFileBubbleContentNode {
|
||||||
|
return contentNode.interactiveFileNode.hasExpandedAudioTranscription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -839,8 +839,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
for node in reactionButtons.removedNodes {
|
for node in reactionButtons.removedNodes {
|
||||||
if animation.isAnimated {
|
if animation.isAnimated {
|
||||||
node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
node?.view.removeFromSuperview()
|
node.view.removeFromSuperview()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
node.view.removeFromSuperview()
|
node.view.removeFromSuperview()
|
||||||
|
|||||||
@ -30,6 +30,17 @@ private struct FetchControls {
|
|||||||
let cancel: () -> Void
|
let cancel: () -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func transcribedText(message: Message) -> EngineAudioTranscriptionResult? {
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
||||||
|
if !attribute.text.isEmpty || !attribute.isPending {
|
||||||
|
return .success(EngineAudioTranscriptionResult.Success(id: attribute.id, text: attribute.text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||||
final class Arguments {
|
final class Arguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -174,9 +185,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
private var streamingCacheStatusFrame: CGRect?
|
private var streamingCacheStatusFrame: CGRect?
|
||||||
private var fileIconImage: UIImage?
|
private var fileIconImage: UIImage?
|
||||||
|
|
||||||
private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .possible
|
private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed
|
||||||
private var transcribedText: EngineAudioTranscriptionResult?
|
|
||||||
private var transcribeDisposable: Disposable?
|
private var transcribeDisposable: Disposable?
|
||||||
|
var hasExpandedAudioTranscription: Bool {
|
||||||
|
if case .expanded = audioTranscriptionState {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
@ -306,17 +323,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.transcribedText == nil {
|
if transcribedText(message: message) == nil {
|
||||||
for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
|
||||||
self.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: attribute.id, text: attribute.text))
|
|
||||||
self.audioTranscriptionState = .collapsed
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.transcribedText == nil {
|
|
||||||
if self.transcribeDisposable == nil {
|
if self.transcribeDisposable == nil {
|
||||||
self.audioTranscriptionState = .inProgress
|
self.audioTranscriptionState = .inProgress
|
||||||
self.requestUpdateLayout(true)
|
self.requestUpdateLayout(true)
|
||||||
@ -361,7 +368,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.transcribeDisposable = nil
|
strongSelf.transcribeDisposable = nil
|
||||||
if let result = result {
|
/*if let result = result {
|
||||||
strongSelf.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: 0, text: result))
|
strongSelf.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: 0, text: result))
|
||||||
} else {
|
} else {
|
||||||
strongSelf.transcribedText = .error
|
strongSelf.transcribedText = .error
|
||||||
@ -371,7 +378,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.audioTranscriptionState = .collapsed
|
strongSelf.audioTranscriptionState = .collapsed
|
||||||
}
|
}
|
||||||
strongSelf.requestUpdateLayout(true)
|
strongSelf.requestUpdateLayout(true)*/
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id)
|
self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id)
|
||||||
@ -380,9 +387,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.transcribeDisposable = nil
|
strongSelf.transcribeDisposable = nil
|
||||||
strongSelf.audioTranscriptionState = .expanded
|
/*strongSelf.audioTranscriptionState = .expanded
|
||||||
strongSelf.transcribedText = result
|
strongSelf.transcribedText = result
|
||||||
strongSelf.requestUpdateLayout(true)
|
strongSelf.requestUpdateLayout(true)*/
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -410,7 +417,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
||||||
|
|
||||||
let currentMessage = self.message
|
let currentMessage = self.message
|
||||||
let transcribedText = self.transcribedText
|
|
||||||
let audioTranscriptionState = self.audioTranscriptionState
|
let audioTranscriptionState = self.audioTranscriptionState
|
||||||
|
|
||||||
return { arguments in
|
return { arguments in
|
||||||
@ -585,7 +591,22 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
let descriptionMaxWidth = max(descriptionLayout.size.width, descriptionMeasuringLayout.size.width)
|
let descriptionMaxWidth = max(descriptionLayout.size.width, descriptionMeasuringLayout.size.width)
|
||||||
let textFont = arguments.presentationData.messageFont
|
let textFont = arguments.presentationData.messageFont
|
||||||
let textString: NSAttributedString?
|
let textString: NSAttributedString?
|
||||||
if let transcribedText = transcribedText, case .expanded = audioTranscriptionState {
|
var updatedAudioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState?
|
||||||
|
|
||||||
|
let transcribedText = transcribedText(message: arguments.message)
|
||||||
|
|
||||||
|
switch audioTranscriptionState {
|
||||||
|
case .inProgress:
|
||||||
|
if transcribedText != nil {
|
||||||
|
updatedAudioTranscriptionState = .expanded
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let effectiveAudioTranscriptionState = updatedAudioTranscriptionState ?? audioTranscriptionState
|
||||||
|
|
||||||
|
if let transcribedText = transcribedText, case .expanded = effectiveAudioTranscriptionState {
|
||||||
switch transcribedText {
|
switch transcribedText {
|
||||||
case let .success(success):
|
case let .success(success):
|
||||||
textString = NSAttributedString(string: success.text, font: textFont, textColor: messageTheme.primaryTextColor)
|
textString = NSAttributedString(string: success.text, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||||
@ -794,6 +815,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
strongSelf.descriptionNode.frame = descriptionFrame
|
strongSelf.descriptionNode.frame = descriptionFrame
|
||||||
strongSelf.descriptionMeasuringNode.frame = CGRect(origin: CGPoint(), size: descriptionMeasuringLayout.size)
|
strongSelf.descriptionMeasuringNode.frame = CGRect(origin: CGPoint(), size: descriptionMeasuringLayout.size)
|
||||||
|
|
||||||
|
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
|
||||||
|
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
|
||||||
|
}
|
||||||
|
|
||||||
if let consumableContentIcon = consumableContentIcon {
|
if let consumableContentIcon = consumableContentIcon {
|
||||||
if strongSelf.consumableContentNode.supernode == nil {
|
if strongSelf.consumableContentNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.consumableContentNode)
|
strongSelf.addSubnode(strongSelf.consumableContentNode)
|
||||||
@ -970,7 +995,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isTranscriptionInProgress = false
|
var isTranscriptionInProgress = false
|
||||||
if case .inProgress = audioTranscriptionState {
|
if case .inProgress = effectiveAudioTranscriptionState {
|
||||||
isTranscriptionInProgress = true
|
isTranscriptionInProgress = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,7 +1034,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
transition: animation.isAnimated ? .easeInOut(duration: 0.3) : .immediate,
|
transition: animation.isAnimated ? .easeInOut(duration: 0.3) : .immediate,
|
||||||
component: AnyComponent(AudioTranscriptionButtonComponent(
|
component: AnyComponent(AudioTranscriptionButtonComponent(
|
||||||
theme: arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing,
|
theme: arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing,
|
||||||
transcriptionState: audioTranscriptionState,
|
transcriptionState: effectiveAudioTranscriptionState,
|
||||||
pressed: {
|
pressed: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -71,6 +71,10 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func update() {
|
func update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,8 +369,8 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
for node in reactionButtons.removedNodes {
|
for node in reactionButtons.removedNodes {
|
||||||
if animation.isAnimated {
|
if animation.isAnimated {
|
||||||
node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
node?.view.removeFromSuperview()
|
node.view.removeFromSuperview()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
node.view.removeFromSuperview()
|
node.view.removeFromSuperview()
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import YuvConversion
|
|||||||
import AnimationCache
|
import AnimationCache
|
||||||
import LottieAnimationCache
|
import LottieAnimationCache
|
||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
|
||||||
private final class CachedChatMessageText {
|
private final class CachedChatMessageText {
|
||||||
let text: String
|
let text: String
|
||||||
@ -64,167 +65,6 @@ private final class InlineStickerItem: Hashable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
|
||||||
static let queue = Queue()
|
|
||||||
|
|
||||||
struct Key: Hashable {
|
|
||||||
var id: MediaId
|
|
||||||
var index: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
private let file: TelegramMediaFile
|
|
||||||
//private var frameSource: QueueLocalObject<AnimatedStickerDirectFrameSource>?
|
|
||||||
private var disposable: Disposable?
|
|
||||||
private var fetchDisposable: Disposable?
|
|
||||||
|
|
||||||
private var isInHierarchyValue: Bool = false
|
|
||||||
var isVisibleForAnimations: Bool = false {
|
|
||||||
didSet {
|
|
||||||
if self.isVisibleForAnimations != oldValue {
|
|
||||||
self.updatePlayback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var displayLink: ConstantDisplayLinkAnimator?
|
|
||||||
|
|
||||||
init(context: AccountContext, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer) {
|
|
||||||
self.file = file
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.disposable = renderer.add(groupId: "inlineEmoji", target: self, cache: cache, itemId: file.resource.id.stringRepresentation, fetch: { writer in
|
|
||||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
|
||||||
|
|
||||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
|
||||||
guard let result = result else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
|
||||||
writer.finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let scale = min(2.0, UIScreenScale)
|
|
||||||
cacheLottieAnimation(data: data, width: Int(24 * scale), height: Int(24 * scale), writer: writer)
|
|
||||||
})
|
|
||||||
|
|
||||||
let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
|
|
||||||
|
|
||||||
return ActionDisposable {
|
|
||||||
dataDisposable.dispose()
|
|
||||||
fetchDisposable.dispose()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/*let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
|
||||||
|
|
||||||
self.disposable = (self.source.directDataPath(attemptSynchronously: false)
|
|
||||||
|> filter { $0 != nil }
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOn(InlineStickerItemLayer.queue)).start(next: { [weak self] path in
|
|
||||||
guard let directData = try? Data(contentsOf: URL(fileURLWithPath: path!), options: [.mappedRead]) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.frameSource = QueueLocalObject(queue: InlineStickerItemLayer.queue, generate: {
|
|
||||||
return AnimatedStickerDirectFrameSource(queue: InlineStickerItemLayer.queue, data: directData, width: Int(24 * UIScreenScale), height: Int(24 * UIScreenScale), cachePathPrefix: pathPrefix, useMetalCache: false, fitzModifier: nil)!
|
|
||||||
})
|
|
||||||
strongSelf.updatePlayback()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()*/
|
|
||||||
}
|
|
||||||
|
|
||||||
override init(layer: Any) {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.disposable?.dispose()
|
|
||||||
self.fetchDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func action(forKey event: String) -> CAAction? {
|
|
||||||
if event == kCAOnOrderIn {
|
|
||||||
self.isInHierarchyValue = true
|
|
||||||
} else if event == kCAOnOrderOut {
|
|
||||||
self.isInHierarchyValue = false
|
|
||||||
}
|
|
||||||
self.updatePlayback()
|
|
||||||
return nullAction
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updatePlayback() {
|
|
||||||
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations
|
|
||||||
|
|
||||||
self.shouldBeAnimating = shouldBePlaying
|
|
||||||
|
|
||||||
/*if shouldBePlaying != (self.displayLink != nil) {
|
|
||||||
if shouldBePlaying {
|
|
||||||
self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
|
|
||||||
self?.loadNextFrame()
|
|
||||||
})
|
|
||||||
self.displayLink?.isPaused = false
|
|
||||||
} else {
|
|
||||||
self.displayLink?.invalidate()
|
|
||||||
self.displayLink = nil
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/*private func loadNextFrame() {
|
|
||||||
guard let frameSource = self.frameSource else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.didRequestFrame = true
|
|
||||||
frameSource.with { [weak self] impl in
|
|
||||||
if let animationFrame = impl.takeFrame(draw: true) {
|
|
||||||
var image: UIImage?
|
|
||||||
|
|
||||||
autoreleasepool {
|
|
||||||
image = generateImagePixel(CGSize(width: CGFloat(animationFrame.width), height: CGFloat(animationFrame.height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
|
||||||
var data = animationFrame.data
|
|
||||||
data.withUnsafeMutableBytes { bytes -> Void in
|
|
||||||
guard let baseAddress = bytes.baseAddress else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch animationFrame.type {
|
|
||||||
case .argb:
|
|
||||||
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
|
||||||
case .yuva:
|
|
||||||
if animationFrame.bytesPerRow <= 0 || animationFrame.height <= 0 || animationFrame.width <= 0 || animationFrame.bytesPerRow * animationFrame.height > bytes.count {
|
|
||||||
assert(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(animationFrame.width), Int32(animationFrame.height), Int32(contextBytesPerRow))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let image = image {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.contents = image.cgImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
private var spoilerTextNode: TextNode?
|
private var spoilerTextNode: TextNode?
|
||||||
@ -723,7 +563,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
||||||
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
||||||
|
|
||||||
strongSelf.updateInlineStickers(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, textLayout: textLayout)
|
strongSelf.updateInlineStickers(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, textLayout: textLayout, placeholderColor: messageTheme.mediaPlaceholderColor)
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
||||||
@ -754,7 +594,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?) {
|
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor) {
|
||||||
var nextIndexById: [MediaId: Int] = [:]
|
var nextIndexById: [MediaId: Int] = [:]
|
||||||
var validIds: [InlineStickerItemLayer.Key] = []
|
var validIds: [InlineStickerItemLayer.Key] = []
|
||||||
|
|
||||||
@ -775,7 +615,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let current = self.inlineStickerItemLayers[id] {
|
if let current = self.inlineStickerItemLayers[id] {
|
||||||
itemLayer = current
|
itemLayer = current
|
||||||
} else {
|
} else {
|
||||||
itemLayer = InlineStickerItemLayer(context: context, file: stickerItem.file, cache: cache, renderer: renderer)
|
itemLayer = InlineStickerItemLayer(context: context, groupId: "inlineEmoji", attemptSynchronousLoad: false, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor)
|
||||||
self.inlineStickerItemLayers[id] = itemLayer
|
self.inlineStickerItemLayers[id] = itemLayer
|
||||||
self.textNode.layer.addSublayer(itemLayer)
|
self.textNode.layer.addSublayer(itemLayer)
|
||||||
itemLayer.isVisibleForAnimations = self.isVisibleForAnimations
|
itemLayer.isVisibleForAnimations = self.isVisibleForAnimations
|
||||||
|
|||||||
@ -667,12 +667,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.textInputBackgroundNode.isUserInteractionEnabled = true
|
self.textInputBackgroundNode.isUserInteractionEnabled = true
|
||||||
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
|
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
|
||||||
|
|
||||||
self.emojiViewProvider = { [weak self] emoji in
|
if let presentationContext = presentationContext {
|
||||||
guard let strongSelf = self, let context = strongSelf.context, let file = strongSelf.context?.animatedEmojiStickers[emoji]?.first?.file else {
|
self.emojiViewProvider = { [weak self, weak presentationContext] emoji in
|
||||||
return UIView()
|
guard let strongSelf = self, let presentationContext = presentationContext, let presentationInterfaceState = strongSelf.presentationInterfaceState, let context = strongSelf.context, let file = strongSelf.context?.animatedEmojiStickers[emoji]?.first?.file else {
|
||||||
|
return UIView()
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmojiTextAttachmentView(context: context, file: file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12))
|
||||||
}
|
}
|
||||||
|
|
||||||
return EmojiTextAttachmentView(context: context, file: file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1715,7 +1717,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.slowmodePlaceholderNode?.isHidden = true
|
self.slowmodePlaceholderNode?.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight)
|
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: minimalHeight - textFieldInsets.bottom - minimalInputHeight)
|
||||||
for (_, button) in self.accessoryItemButtons.reversed() {
|
for (_, button) in self.accessoryItemButtons.reversed() {
|
||||||
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
||||||
button.updateLayout(size: buttonSize)
|
button.updateLayout(size: buttonSize)
|
||||||
|
|||||||
@ -90,6 +90,23 @@ static bool notyfyingShiftState = false;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface CADisplayLink (FrameRateRangeOverride)
|
||||||
|
|
||||||
|
- (void)_65087dc8_setPreferredFrameRateRange:(CAFrameRateRange)range API_AVAILABLE(ios(15.0));
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation CADisplayLink (FrameRateRangeOverride)
|
||||||
|
|
||||||
|
- (void)_65087dc8_setPreferredFrameRateRange:(CAFrameRateRange)range API_AVAILABLE(ios(15.0)) {
|
||||||
|
float maxFps = [UIScreen mainScreen].maximumFramesPerSecond;
|
||||||
|
range = CAFrameRateRangeMake(maxFps, maxFps, maxFps);
|
||||||
|
|
||||||
|
[self _65087dc8_setPreferredFrameRateRange:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation UIViewController (Navigation)
|
@implementation UIViewController (Navigation)
|
||||||
|
|
||||||
+ (void)load
|
+ (void)load
|
||||||
@ -105,6 +122,10 @@ static bool notyfyingShiftState = false;
|
|||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)];
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
||||||
|
|
||||||
|
if (@available(iOS 15.0, *)) {
|
||||||
|
[RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -150,18 +150,6 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
private func updateIsVisible(_ isVisible: Bool) {
|
private func updateIsVisible(_ isVisible: Bool) {
|
||||||
if isVisible {
|
if isVisible {
|
||||||
if self.displayLink == nil {
|
if self.displayLink == nil {
|
||||||
final class DisplayLinkTarget: NSObject {
|
|
||||||
private let f: () -> Void
|
|
||||||
|
|
||||||
init(_ f: @escaping () -> Void) {
|
|
||||||
self.f = f
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func event() {
|
|
||||||
self.f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in
|
let displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user