Animation and other improvements

This commit is contained in:
Ali 2022-05-27 18:45:05 +04:00
parent 0d38a9bd08
commit bacd88a1ff
37 changed files with 498 additions and 426 deletions

View File

@ -12,7 +12,7 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
public func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
let file = 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> {
@ -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)
}
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)
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)
}

View File

@ -8,11 +8,29 @@
//
#import <AsyncDisplayKit/ASTextKitComponents.h>
#import "ASTextKitContext.h"
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASMainThreadDeallocation.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
@end
@ -118,7 +136,8 @@
components.layoutManager = 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.layoutManager addTextContainer:components.textContainer];

View File

@ -50,4 +50,8 @@ AS_SUBCLASSING_RESTRICTED
@end
@interface ASCustomTextContainer : NSTextContainer
@end
#endif

View File

@ -58,7 +58,7 @@
[_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.
_textContainer.lineFragmentPadding = 0;
_textContainer.lineBreakMode = lineBreakMode;

View File

@ -316,13 +316,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
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 {
return UIView()
}
return EmojiTextAttachmentView(context: context, file: file)
}
}*/
self.updateSendButtonEnabled(isCaption || isAttachment, animated: false)

View File

@ -834,9 +834,13 @@ public final class ReactionNodePool {
private var views: [ReactionButtonAsyncNode] = []
func putBack(view: ReactionButtonAsyncNode) {
assert(view.superview == nil)
if self.views.count < 64 {
view.reset()
self.views.append(view)
}
}
func take() -> Item {
if !self.views.isEmpty {
@ -892,6 +896,12 @@ public final class ReactionButtonsAsyncLayoutContainer {
public init() {
}
deinit {
for (_, button) in self.buttons {
button.view.removeFromSuperview()
}
}
public func update(
context: AccountContext,
action: @escaping (String) -> Void,

View File

@ -24,6 +24,7 @@ import UIKitRuntimeUtils
}
if let completion = self.completion {
completion(flag)
self.completion = nil
}
}
}
@ -83,6 +84,9 @@ public extension CALayer {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
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
} else if timingFunction == kCAMediaTimingFunctionSpring {
let animation = makeSpringAnimation(keyPath)
@ -108,6 +112,10 @@ public extension CALayer {
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
} else {
let k = Float(UIView.animationDurationFactor())
@ -138,6 +146,10 @@ public extension CALayer {
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
}
}
@ -196,6 +208,10 @@ public extension CALayer {
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)
}
@ -225,6 +241,10 @@ public extension CALayer {
animation.speed = speed * Float(animation.duration / duration)
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
}
@ -257,6 +277,10 @@ public extension CALayer {
animation.speed = speed * Float(animation.duration / duration)
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)
}
@ -284,6 +308,10 @@ public extension CALayer {
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)
}

View File

@ -1,14 +1,14 @@
import Foundation
import UIKit
private final class DisplayLinkTarget: NSObject {
public final class DisplayLinkTarget: NSObject {
private let f: () -> Void
init(_ f: @escaping () -> Void) {
public init(_ f: @escaping () -> Void) {
self.f = f
}
@objc func event() {
@objc public func event() {
self.f()
}
}

View File

@ -376,6 +376,30 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
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]? {
get {
var accessibilityElements: [Any] = []
@ -789,6 +813,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.isDeceleratingAfterTracking = true
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: false)
self.isAuxiliaryDisplayLinkEnabled = true
} else {
self.isDeceleratingAfterTracking = false
self.resetHeaderItemsFlashTimer(start: true)
@ -797,6 +823,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.lastContentOffsetTimestamp = 0.0
self.didEndScrolling?(false)
self.isAuxiliaryDisplayLinkEnabled = false
}
self.endedInteractiveDragging(self.touchesPosition)
}
@ -807,6 +834,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.resetHeaderItemsFlashTimer(start: true)
self.updateHeaderItemsFlashing(animated: true)
self.resetScrollIndicatorFlashTimer(start: true)
self.isAuxiliaryDisplayLinkEnabled = false
if !scrollView.isTracking {
self.didEndScrolling?(true)
}
@ -3192,6 +3220,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
springAnimation.isRemovedOnCompletion = true
springAnimation.isAdditive = true
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())
var speed: Float = 1.0
@ -3223,6 +3254,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
basicAnimation.isRemovedOnCompletion = 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")
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y)
@ -3231,6 +3265,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
reverseBasicAnimation.isRemovedOnCompletion = 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
reverseAnimation = reverseBasicAnimation
@ -3245,6 +3282,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
basicAnimation.isRemovedOnCompletion = 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")
reverseBasicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
@ -3253,6 +3293,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
reverseBasicAnimation.isRemovedOnCompletion = 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
reverseAnimation = reverseBasicAnimation
@ -3267,6 +3310,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
basicAnimation.isRemovedOnCompletion = 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")
reverseBasicAnimation.timingFunction = ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction
@ -3275,6 +3321,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity)
reverseBasicAnimation.isRemovedOnCompletion = 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
reverseAnimation = reverseBasicAnimation

View File

@ -176,17 +176,6 @@ open class ManagedAnimationNode: ASDisplayNode {
self.imageNode.displaysAsynchronously = false
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)?
self.displayLink = CADisplayLink(target: DisplayLinkTarget {
displayLinkUpdate?()

View File

@ -21,6 +21,38 @@ private func decodeStickerThumbnailData(_ data: Data) -> 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 {
private var backdropNode: 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)
let bounds = CGRect(origin: CGPoint(), size: size)
let image = 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(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()
}
})
let image = generateStickerPlaceholderImage(data: data, size: size, imageSize: imageSize, backgroundColor: backgroundColor, foregroundColor: .black)
if backgroundColor == nil {
self.foregroundNode.image = nil

View File

@ -360,7 +360,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1678949555] = { return Api.InputWebDocument.parse_inputWebDocument($0) }
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($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[-146520221] = { return Api.JSONValue.parse_jsonArray($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[196268545] = { return Api.Update.parse_updateStickerSetsOrder($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[88680979] = { return Api.Update.parse_updateUserPhone($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[816245886] = { return Api.messages.Stickers.parse_stickers($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[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }

View File

@ -599,6 +599,7 @@ public extension Api {
case updateStickerSets
case updateStickerSetsOrder(flags: Int32, order: [Int64])
case updateTheme(theme: Api.Theme)
case updateTranscribeAudio(flags: Int32, transcriptionId: Int64, text: String)
case updateUserName(userId: Int64, firstName: String, lastName: String, username: String)
case updateUserPhone(userId: Int64, phone: String)
case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool)
@ -1423,6 +1424,14 @@ public extension Api {
}
theme.serialize(buffer, true)
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):
if boxed {
buffer.appendInt32(-1007549728)
@ -1667,6 +1676,8 @@ public extension Api {
return ("updateStickerSetsOrder", [("flags", String(describing: flags)), ("order", String(describing: order))])
case .updateTheme(let 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):
return ("updateUserName", [("userId", String(describing: userId)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("username", String(describing: username))])
case .updateUserPhone(let userId, let phone):
@ -3325,6 +3336,23 @@ public extension Api {
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? {
var _1: Int64?
_1 = reader.readInt64()

View File

@ -490,14 +490,15 @@ public extension Api.messages {
}
public extension Api.messages {
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) {
switch self {
case .transcribedAudio(let transcriptionId, let text):
case .transcribedAudio(let flags, let transcriptionId, let text):
if boxed {
buffer.appendInt32(-1077051894)
buffer.appendInt32(-1821037486)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(transcriptionId, buffer: buffer, boxed: false)
serializeString(text, buffer: buffer, boxed: false)
break
@ -506,20 +507,23 @@ public extension Api.messages {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .transcribedAudio(let transcriptionId, let text):
return ("transcribedAudio", [("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
case .transcribedAudio(let flags, let transcriptionId, let text):
return ("transcribedAudio", [("flags", String(describing: flags)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
}
}
public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? {
var _1: Int64?
_1 = reader.readInt64()
var _2: String?
_2 = parseString(reader)
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
if _c1 && _c2 {
return Api.messages.TranscribedAudio.transcribedAudio(transcriptionId: _1!, text: _2!)
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!)
}
else {
return nil

View File

@ -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 {
static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
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 {
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()

View File

@ -898,13 +898,13 @@ public extension Api {
}
public extension Api {
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) {
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 {
buffer.appendInt32(215516896)
buffer.appendInt32(-1197014651)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
@ -919,14 +919,15 @@ public extension Api {
for item in suggestedTipAmounts! {
serializeInt64(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 8) != 0 {serializeString(recurrentTermsUrl!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts):
return ("invoice", [("flags", String(describing: flags)), ("currency", String(describing: currency)), ("prices", String(describing: prices)), ("maxTipAmount", String(describing: maxTipAmount)), ("suggestedTipAmounts", String(describing: 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)), ("recurrentTermsUrl", String(describing: recurrentTermsUrl))])
}
}
@ -945,13 +946,16 @@ public extension Api {
if Int(_1!) & Int(1 << 8) != 0 {if let _ = reader.readInt32() {
_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 _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5)
let _c6 = (Int(_1!) & Int(1 << 8) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, recurrentTermsUrl: _6)
}
else {
return nil

View File

@ -1101,6 +1101,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
/*case let .updateTranscribeAudio(flags, transcriptionId, text):
break*/
case let .updateNotifySettings(apiPeer, apiNotificationSettings):
switch apiPeer {
case let .notifyPeer(peer):

View File

@ -3,24 +3,28 @@ import Postbox
public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
public let id: Int64
public let text: String
public let isPending: Bool
public var associatedPeerIds: [PeerId] {
return []
}
public init(id: Int64, text: String) {
public init(id: Int64, text: String, isPending: Bool) {
self.id = id
self.text = text
self.isPending = isPending
}
required public init(decoder: PostboxDecoder) {
self.id = decoder.decodeInt64ForKey("id", orElse: 0)
self.text = decoder.decodeStringForKey("text", orElse: "")
self.isPending = decoder.decodeBoolForKey("isPending", orElse: false)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.id, forKey: "id")
encoder.encodeString(self.text, forKey: "text")
encoder.encodeBool(self.isPending, forKey: "isPending")
}
public static func ==(lhs: AudioTranscriptionMessageAttribute, rhs: AudioTranscriptionMessageAttribute) -> Bool {
@ -30,6 +34,9 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
if lhs.text != rhs.text {
return false
}
if lhs.isPending != rhs.isPending {
return false
}
return true
}
}

View File

@ -64,12 +64,14 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
return postbox.transaction { transaction -> EngineAudioTranscriptionResult in
switch result {
case let .transcribedAudio(transcriptionId, text):
case let .transcribedAudio(flags, transcriptionId, text):
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
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))
})

View File

@ -124,7 +124,7 @@ public enum BotPaymentFormRequestError {
extension BotPaymentInvoice {
init(apiInvoice: Api.Invoice) {
switch apiInvoice {
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts):
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, _):
var fields = BotPaymentInvoiceFields()
if (flags & (1 << 1)) != 0 {
fields.insert(.name)

View File

@ -43,6 +43,7 @@ public protocol AnimationCacheItemWriter: AnyObject {
public protocol AnimationCache: AnyObject {
func get(sourceId: String, fetch: @escaping (AnimationCacheItemWriter) -> Disposable) -> Signal<AnimationCacheItem?, NoError>
func getSynchronously(sourceId: String) -> AnimationCacheItem?
}
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
@ -403,4 +416,10 @@ public final class AnimationCacheImpl: AnimationCache {
}
|> runOn(self.queue)
}
public func getSynchronously(sourceId: String) -> AnimationCacheItem? {
return self.impl.syncWith { impl -> AnimationCacheItem? in
return impl.getSynchronously(sourceId: sourceId)
}
}
}

View File

@ -8,7 +8,6 @@ import LottieAnimationComponent
public final class AudioTranscriptionButtonComponent: Component {
public enum TranscriptionState {
case possible
case inProgress
case expanded
case collapsed
@ -130,8 +129,6 @@ public final class AudioTranscriptionButtonComponent: Component {
let animationName: String
switch component.transcriptionState {
case .possible:
animationName = "voiceToText"
case .inProgress:
animationName = "voiceToText"
case .collapsed:

View File

@ -19,6 +19,10 @@ swift_library(
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/YuvConversion:YuvConversion",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/LottieAnimationCache:LottieAnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -9,104 +9,80 @@ import AccountContext
import YuvConversion
import TelegramCore
import Postbox
import AnimationCache
import LottieAnimationCache
import MultiAnimationRenderer
import ShimmerEffect
private final class InlineStickerItemLayer: SimpleLayer {
static let queue = Queue()
public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
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 source: AnimatedStickerNodeSource
private var frameSource: QueueLocalObject<AnimatedStickerDirectFrameSource>?
private var disposable: Disposable?
private var fetchDisposable: Disposable?
private var isInHierarchyValue: Bool = false
var isVisibleForAnimations: Bool = false {
public var isVisibleForAnimations: Bool = false {
didSet {
if self.isVisibleForAnimations != oldValue {
self.updatePlayback()
}
}
}
private var displayLink: ConstantDisplayLinkAnimator?
init(context: AccountContext, file: TelegramMediaFile) {
self.source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
public init(context: AccountContext, groupId: String, attemptSynchronousLoad: Bool, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.file = file
super.init()
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
let width = Int(24 * UIScreenScale)
let height = Int(24 * UIScreenScale)
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 {
if attemptSynchronousLoad {
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation) {
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) {
self.contents = image.cgImage
}
}
}
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 {
self.disposable = renderer.add(groupId: groupId, 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
}
Queue.mainQueue().async {
guard let strongSelf = self else {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
writer.finish()
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)!
})
strongSelf.updatePlayback()
}
let scale = min(2.0, UIScreenScale)
cacheLottieAnimation(data: data, width: Int(24 * scale), height: Int(24 * scale), writer: writer)
})
self.fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
return ActionDisposable {
dataDisposable.dispose()
fetchDisposable.dispose()
}
})
}
override init(layer: Any) {
guard let layer = layer as? InlineStickerItemLayer else {
override public init(layer: Any) {
preconditionFailure()
}
self.source = layer.source
self.file = layer.file
super.init(layer: layer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
@ -117,7 +93,7 @@ private final class InlineStickerItemLayer: SimpleLayer {
self.fetchDisposable?.dispose()
}
override func action(forKey event: String) -> CAAction? {
override public func action(forKey event: String) -> CAAction? {
if event == kCAOnOrderIn {
self.isInHierarchyValue = true
} else if event == kCAOnOrderOut {
@ -128,72 +104,17 @@ private final class InlineStickerItemLayer: SimpleLayer {
}
private func updatePlayback() {
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations && self.frameSource != nil
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
}
}
}
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations
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
}
}
}
}
self.shouldBeAnimating = shouldBePlaying
}
}
public final class EmojiTextAttachmentView: UIView {
private let contentLayer: InlineStickerItemLayer
public init(context: AccountContext, file: TelegramMediaFile) {
self.contentLayer = InlineStickerItemLayer(context: context, file: file)
public init(context: AccountContext, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.contentLayer = InlineStickerItemLayer(context: context, groupId: "textInputView", attemptSynchronousLoad: true, file: file, cache: cache, renderer: renderer, placeholderColor: placeholderColor)
super.init(frame: CGRect())

View File

@ -6,6 +6,7 @@ import AnimationCache
public protocol MultiAnimationRenderer: AnyObject {
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 {
@ -159,6 +160,19 @@ private final class ItemAnimationContext {
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() {
var isPlaying = true
if self.item == nil {
@ -268,6 +282,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
}
let index = itemContext.targets.add(Weak(target))
itemContext.updateAddedTarget(target: target)
let deinitIndex = target.deinitCallbacks.add { [weak self, weak itemContext] in
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() {
var isPlaying = false
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() {
var isPlaying = false
for (_, groupContext) in self.groupContexts {

View File

@ -972,7 +972,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = combineLatest(queue: .mainQueue(),
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(),
peerAllowedReactions(context: strongSelf.context, peerId: topMessage.id.peerId),
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)

View File

@ -2473,7 +2473,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var attributes: [MessageAttribute] = []
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))
}
}

View File

@ -294,14 +294,16 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
}
}
if chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 && chatPresentationInterfaceState.interfaceState.forwardMessageIds == nil {
if chatPresentationInterfaceState.hasScheduledMessages {
let isTextEmpty = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0
if chatPresentationInterfaceState.interfaceState.forwardMessageIds == nil {
if isTextEmpty && chatPresentationInterfaceState.hasScheduledMessages {
accessoryItems.append(.scheduledMessages)
}
var stickersEnabled = true
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))
}
if peer.hasBannedPermission(.banSendStickers) != nil {
@ -312,11 +314,17 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
stickersEnabled = false
}
}
if chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands {
if isTextEmpty && chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands {
accessoryItems.append(.commands)
}
#if DEBUG
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)
}
}

View File

@ -374,11 +374,16 @@ func updatedChatEditInterfaceMessageState(state: ChatPresentationInterfaceState,
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 {
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 {
let message = messages[0]
@ -704,7 +709,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
var hasRateTranscription = false
if let audioTranscription = audioTranscription {
if hasExpandedAudioTranscription, let audioTranscription = audioTranscription {
hasRateTranscription = true
actions.insert(.custom(ChatRateTranscriptionContextItem(context: context, message: message, action: { [weak context] value in
guard let context = context else {
@ -812,7 +817,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
for attribute in message.attributes {
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
if hasExpandedAudioTranscription, let attribute = attribute as? AudioTranscriptionMessageAttribute {
if !messageText.isEmpty {
messageText.append("\n")
}

View File

@ -4026,4 +4026,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
return nil
}
func hasExpandedAudioTranscription() -> Bool {
for contentNode in self.contentNodes {
if let contentNode = contentNode as? ChatMessageFileBubbleContentNode {
return contentNode.interactiveFileNode.hasExpandedAudioTranscription
}
}
return false
}
}

View File

@ -839,8 +839,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
for node in reactionButtons.removedNodes {
if animation.isAnimated {
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.removeFromSuperview()
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
node.view.removeFromSuperview()
})
} else {
node.view.removeFromSuperview()

View File

@ -30,6 +30,17 @@ private struct FetchControls {
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 Arguments {
let context: AccountContext
@ -174,9 +185,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
private var streamingCacheStatusFrame: CGRect?
private var fileIconImage: UIImage?
private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .possible
private var transcribedText: EngineAudioTranscriptionResult?
private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed
private var transcribeDisposable: Disposable?
var hasExpandedAudioTranscription: Bool {
if case .expanded = audioTranscriptionState {
return true
} else {
return false
}
}
override init() {
self.titleNode = TextNode()
@ -306,17 +323,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
if self.transcribedText == 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 transcribedText(message: message) == nil {
if self.transcribeDisposable == nil {
self.audioTranscriptionState = .inProgress
self.requestUpdateLayout(true)
@ -361,7 +368,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
strongSelf.transcribeDisposable = nil
if let result = result {
/*if let result = result {
strongSelf.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: 0, text: result))
} else {
strongSelf.transcribedText = .error
@ -371,7 +378,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
} else {
strongSelf.audioTranscriptionState = .collapsed
}
strongSelf.requestUpdateLayout(true)
strongSelf.requestUpdateLayout(true)*/
})
} else {
self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id)
@ -380,9 +387,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
strongSelf.transcribeDisposable = nil
strongSelf.audioTranscriptionState = .expanded
/*strongSelf.audioTranscriptionState = .expanded
strongSelf.transcribedText = result
strongSelf.requestUpdateLayout(true)
strongSelf.requestUpdateLayout(true)*/
})
}
}
@ -410,7 +417,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let statusLayout = self.dateAndStatusNode.asyncLayout()
let currentMessage = self.message
let transcribedText = self.transcribedText
let audioTranscriptionState = self.audioTranscriptionState
return { arguments in
@ -585,7 +591,22 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let descriptionMaxWidth = max(descriptionLayout.size.width, descriptionMeasuringLayout.size.width)
let textFont = arguments.presentationData.messageFont
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 {
case let .success(success):
textString = NSAttributedString(string: success.text, font: textFont, textColor: messageTheme.primaryTextColor)
@ -794,6 +815,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.descriptionNode.frame = descriptionFrame
strongSelf.descriptionMeasuringNode.frame = CGRect(origin: CGPoint(), size: descriptionMeasuringLayout.size)
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
}
if let consumableContentIcon = consumableContentIcon {
if strongSelf.consumableContentNode.supernode == nil {
strongSelf.addSubnode(strongSelf.consumableContentNode)
@ -970,7 +995,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
var isTranscriptionInProgress = false
if case .inProgress = audioTranscriptionState {
if case .inProgress = effectiveAudioTranscriptionState {
isTranscriptionInProgress = true
}
@ -1009,7 +1034,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
transition: animation.isAnimated ? .easeInOut(duration: 0.3) : .immediate,
component: AnyComponent(AudioTranscriptionButtonComponent(
theme: arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing,
transcriptionState: audioTranscriptionState,
transcriptionState: effectiveAudioTranscriptionState,
pressed: {
guard let strongSelf = self else {
return

View File

@ -71,6 +71,10 @@ final class MessageReactionButtonsNode: ASDisplayNode {
super.init()
}
deinit {
}
func update() {
}
@ -365,8 +369,8 @@ final class MessageReactionButtonsNode: ASDisplayNode {
for node in reactionButtons.removedNodes {
if animation.isAnimated {
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.removeFromSuperview()
node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
node.view.removeFromSuperview()
})
} else {
node.view.removeFromSuperview()

View File

@ -18,6 +18,7 @@ import YuvConversion
import AnimationCache
import LottieAnimationCache
import MultiAnimationRenderer
import EmojiTextAttachmentView
private final class CachedChatMessageText {
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 {
private let textNode: TextNode
private var spoilerTextNode: TextNode?
@ -723,7 +563,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.textAccessibilityOverlayNode.frame = textFrame
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 {
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 validIds: [InlineStickerItemLayer.Key] = []
@ -775,7 +615,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let current = self.inlineStickerItemLayers[id] {
itemLayer = current
} 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.textNode.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = self.isVisibleForAnimations

View File

@ -667,12 +667,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.textInputBackgroundNode.isUserInteractionEnabled = true
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
self.emojiViewProvider = { [weak self] emoji in
guard let strongSelf = self, let context = strongSelf.context, let file = strongSelf.context?.animatedEmojiStickers[emoji]?.first?.file else {
if let presentationContext = presentationContext {
self.emojiViewProvider = { [weak self, weak presentationContext] emoji in
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)
return EmojiTextAttachmentView(context: context, file: file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12))
}
}
}
@ -1715,7 +1717,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
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() {
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
button.updateLayout(size: buttonSize)

View File

@ -90,6 +90,23 @@ static bool notyfyingShiftState = false;
@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)
+ (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(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
[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:)];
}
});
}

View File

@ -150,18 +150,6 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
private func updateIsVisible(_ isVisible: Bool) {
if isVisible {
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
guard let strongSelf = self else {
return