mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Reactions
This commit is contained in:
parent
fb0824ed8b
commit
f1e4e2dc7b
@ -7967,3 +7967,5 @@ Sorry for the inconvenience.";
|
||||
"Login.PhoneNumberConfirmation" = "Is this the correct number?";
|
||||
"Login.Edit" = "Edit";
|
||||
"Login.Yes" = "Yes";
|
||||
|
||||
"Settings.ChangeProfilePhoto" = "Change Profile Photo";
|
||||
|
@ -32,10 +32,13 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
public var currentFrameCount: Int {
|
||||
get {
|
||||
guard let lottieInstance = self.lottieInstance else {
|
||||
if let lottieInstance = self.lottieInstance {
|
||||
return Int(lottieInstance.frameCount)
|
||||
} else if let videoSource = self.videoSource {
|
||||
return Int(videoSource.frameRate)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
return Int(lottieInstance.frameCount)
|
||||
} set(value) {
|
||||
}
|
||||
}
|
||||
@ -64,6 +67,7 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
private var playbackSize: CGSize?
|
||||
|
||||
private var lottieInstance: LottieInstance?
|
||||
private var videoSource: AnimatedStickerFrameSource?
|
||||
private var frameIndex: Int = 0
|
||||
private var playbackMode: AnimatedStickerPlaybackMode = .loop
|
||||
|
||||
@ -99,23 +103,30 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
guard let strongSelf = self, let path = path else {
|
||||
return
|
||||
}
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
|
||||
return
|
||||
|
||||
if source.isVideo {
|
||||
if let videoSource = makeVideoStickerDirectFrameSource(queue: DirectAnimatedStickerNode.sharedQueue, path: path, width: width, height: height, cachePathPrefix: nil, unpremultiplyAlpha: false) {
|
||||
strongSelf.setupPlayback(videoSource: videoSource)
|
||||
}
|
||||
} else {
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
|
||||
return
|
||||
}
|
||||
|
||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
|
||||
|
||||
guard let lottieInstance = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
print("Could not load sticker data")
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setupPlayback(lottieInstance: lottieInstance)
|
||||
}
|
||||
|
||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
|
||||
|
||||
guard let lottieInstance = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
print("Could not load sticker data")
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setupPlayback(lottieInstance: lottieInstance)
|
||||
})
|
||||
}
|
||||
|
||||
private func updatePlayback() {
|
||||
let isPlaying = self.visibility && self.lottieInstance != nil
|
||||
let isPlaying = self.visibility && (self.lottieInstance != nil || self.videoSource != nil)
|
||||
if self.isPlaying != isPlaying {
|
||||
self.isPlaying = isPlaying
|
||||
|
||||
@ -132,8 +143,15 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
|
||||
private func startNextFrameTimerIfNeeded() {
|
||||
if self.nextFrameTimer == nil, let lottieInstance = self.lottieInstance, self.frameImages[self.frameIndex] != nil {
|
||||
let nextFrameTimer = SwiftSignalKit.Timer(timeout: 1.0 / Double(lottieInstance.frameRate), repeat: false, completion: { [weak self] in
|
||||
var frameRate: Double?
|
||||
if let lottieInstance = self.lottieInstance {
|
||||
frameRate = Double(lottieInstance.frameRate)
|
||||
} else if let videoSource = self.videoSource {
|
||||
frameRate = Double(videoSource.frameRate)
|
||||
}
|
||||
|
||||
if self.nextFrameTimer == nil, let frameRate = frameRate, self.frameImages[self.frameIndex] != nil {
|
||||
let nextFrameTimer = SwiftSignalKit.Timer(timeout: 1.0 / frameRate, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -146,11 +164,17 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
|
||||
private func advanceFrameIfPossible() {
|
||||
guard let lottieInstance = self.lottieInstance else {
|
||||
var frameCount: Int?
|
||||
if let lottieInstance = self.lottieInstance {
|
||||
frameCount = Int(lottieInstance.frameCount)
|
||||
} else if let videoSource = self.videoSource {
|
||||
frameCount = Int(videoSource.frameCount)
|
||||
}
|
||||
guard let frameCount = frameCount else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.frameIndex == Int(lottieInstance.frameCount) - 1 {
|
||||
if self.frameIndex == frameCount - 1 {
|
||||
switch self.playbackMode {
|
||||
case .loop:
|
||||
self.completed(false)
|
||||
@ -176,7 +200,7 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
}
|
||||
|
||||
let nextFrameIndex = (self.frameIndex + 1) % Int(lottieInstance.frameCount)
|
||||
let nextFrameIndex = (self.frameIndex + 1) % frameCount
|
||||
self.frameIndex = nextFrameIndex
|
||||
|
||||
self.updateFrameImageIfNeeded()
|
||||
@ -184,13 +208,19 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
|
||||
private func updateFrameImageIfNeeded() {
|
||||
guard let lottieInstance = self.lottieInstance else {
|
||||
var frameCount: Int?
|
||||
if let lottieInstance = self.lottieInstance {
|
||||
frameCount = Int(lottieInstance.frameCount)
|
||||
} else if let videoSource = self.videoSource {
|
||||
frameCount = Int(videoSource.frameCount)
|
||||
}
|
||||
guard let frameCount = frameCount else {
|
||||
return
|
||||
}
|
||||
|
||||
var allowedIndices: [Int] = []
|
||||
for i in 0 ..< 2 {
|
||||
let mappedIndex = (self.frameIndex + i) % Int(lottieInstance.frameCount)
|
||||
let mappedIndex = (self.frameIndex + i) % frameCount
|
||||
allowedIndices.append(mappedIndex)
|
||||
}
|
||||
|
||||
@ -213,7 +243,7 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
if let image = self.frameImages[self.frameIndex] {
|
||||
self.layer.contents = image.cgImage
|
||||
|
||||
self.frameUpdated(self.frameIndex, Int(lottieInstance.frameCount))
|
||||
self.frameUpdated(self.frameIndex, frameCount)
|
||||
|
||||
if !self.didComplete {
|
||||
self.startNextFrameTimerIfNeeded()
|
||||
@ -227,20 +257,29 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
|
||||
private func updateLoadFrameTasks() {
|
||||
guard let lottieInstance = self.lottieInstance else {
|
||||
var frameCount: Int?
|
||||
if let lottieInstance = self.lottieInstance {
|
||||
frameCount = Int(lottieInstance.frameCount)
|
||||
} else if let videoSource = self.videoSource {
|
||||
frameCount = Int(videoSource.frameCount)
|
||||
}
|
||||
guard let frameCount = frameCount else {
|
||||
return
|
||||
}
|
||||
|
||||
let frameIndex = self.frameIndex % Int(lottieInstance.frameCount)
|
||||
let frameIndex = self.frameIndex % frameCount
|
||||
if self.frameImages[frameIndex] == nil {
|
||||
self.maybeStartLoadFrameTask(frameIndex: frameIndex)
|
||||
} else {
|
||||
self.maybeStartLoadFrameTask(frameIndex: (frameIndex + 1) % Int(lottieInstance.frameCount))
|
||||
self.maybeStartLoadFrameTask(frameIndex: (frameIndex + 1) % frameCount)
|
||||
}
|
||||
}
|
||||
|
||||
private func maybeStartLoadFrameTask(frameIndex: Int) {
|
||||
guard let lottieInstance = self.lottieInstance, let playbackSize = self.playbackSize else {
|
||||
guard self.lottieInstance != nil || self.videoSource != nil else {
|
||||
return
|
||||
}
|
||||
guard let playbackSize = self.playbackSize else {
|
||||
return
|
||||
}
|
||||
if self.loadFrameTasks[frameIndex] != nil {
|
||||
@ -250,14 +289,27 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
let task = LoadFrameTask()
|
||||
self.loadFrameTasks[frameIndex] = task
|
||||
|
||||
let lottieInstance = self.lottieInstance
|
||||
let videoSource = self.videoSource
|
||||
|
||||
DirectAnimatedStickerNode.sharedQueue.async { [weak self] in
|
||||
var image: UIImage?
|
||||
|
||||
if !task.isCancelled {
|
||||
let drawingContext = DrawingContext(size: playbackSize, scale: 1.0, opaque: false, clear: false)
|
||||
lottieInstance.renderFrame(with: Int32(frameIndex), into: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(drawingContext.scaledSize.width), height: Int32(drawingContext.scaledSize.height), bytesPerRow: Int32(drawingContext.bytesPerRow))
|
||||
|
||||
image = drawingContext.generateImage()
|
||||
if let lottieInstance = lottieInstance {
|
||||
let drawingContext = DrawingContext(size: playbackSize, scale: 1.0, opaque: false, clear: false)
|
||||
lottieInstance.renderFrame(with: Int32(frameIndex), into: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(drawingContext.scaledSize.width), height: Int32(drawingContext.scaledSize.height), bytesPerRow: Int32(drawingContext.bytesPerRow))
|
||||
|
||||
image = drawingContext.generateImage()
|
||||
} else if let videoSource = videoSource {
|
||||
if let frame = videoSource.takeFrame(draw: true) {
|
||||
let drawingContext = DrawingContext(size: CGSize(width: frame.width, height: frame.height), scale: 1.0, opaque: false, clear: false, bytesPerRow: frame.bytesPerRow)
|
||||
|
||||
frame.data.copyBytes(to: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), from: 0 ..< min(frame.data.count, drawingContext.length))
|
||||
|
||||
image = drawingContext.generateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
@ -284,6 +336,12 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
self.updatePlayback()
|
||||
}
|
||||
|
||||
private func setupPlayback(videoSource: AnimatedStickerFrameSource) {
|
||||
self.videoSource = videoSource
|
||||
|
||||
self.updatePlayback()
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
}
|
||||
|
||||
|
@ -594,7 +594,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
if let image = ReactionImageCache.shared.get(reaction: layout.spec.component.reaction.value) {
|
||||
iconView.imageView.image = image
|
||||
} else {
|
||||
self.iconImageDisposable.set((reactionStaticImage(context: layout.spec.component.context, animation: file, pixelSize: CGSize(width: 32.0 * UIScreenScale, height: 32.0 * UIScreenScale))
|
||||
self.iconImageDisposable.set((reactionStaticImage(context: layout.spec.component.context, animation: file, pixelSize: CGSize(width: 32.0 * UIScreenScale, height: 32.0 * UIScreenScale), queue: sharedReactionStaticImage)
|
||||
|> filter { data in
|
||||
return data.isComplete
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ swift_library(
|
||||
"//submodules/WebPBinding:WebPBinding",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -11,32 +11,57 @@ import UIKit
|
||||
import WebPBinding
|
||||
import RLottieBinding
|
||||
import GZip
|
||||
import AnimationCache
|
||||
import EmojiTextAttachmentView
|
||||
|
||||
public func reactionStaticImage(context: AccountContext, animation: TelegramMediaFile, pixelSize: CGSize) -> Signal<EngineMediaResource.ResourceData, NoError> {
|
||||
public let sharedReactionStaticImage = Queue(name: "SharedReactionStaticImage", qos: .default)
|
||||
|
||||
public func reactionStaticImage(context: AccountContext, animation: TelegramMediaFile, pixelSize: CGSize, queue: Queue) -> Signal<EngineMediaResource.ResourceData, NoError> {
|
||||
return context.engine.resources.custom(id: "\(animation.resource.id.stringRepresentation):reaction-static-\(pixelSize.width)x\(pixelSize.height)-v10", fetch: EngineMediaResource.Fetch {
|
||||
return Signal { subscriber in
|
||||
let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: animation.resource)).start()
|
||||
let dataDisposable = context.account.postbox.mediaBox.resourceData(animation.resource).start(next: { data in
|
||||
if !data.complete {
|
||||
return
|
||||
}
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||
return
|
||||
}
|
||||
guard let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
guard let instance = LottieInstance(data: unpackedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
return
|
||||
|
||||
let type: AnimationCacheAnimationType
|
||||
if animation.isVideoSticker || animation.isVideoEmoji {
|
||||
type = .video
|
||||
} else if animation.isAnimatedSticker {
|
||||
type = .lottie
|
||||
} else {
|
||||
type = .still
|
||||
}
|
||||
let fetchFrame = animationCacheFetchFile(context: context, resource: MediaResourceReference.standalone(resource: animation.resource), type: type, keyframeOnly: true)
|
||||
|
||||
class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
let queue: Queue
|
||||
private let frameReceived: (UIImage) -> Void
|
||||
|
||||
init(queue: Queue, frameReceived: @escaping (UIImage) -> Void) {
|
||||
self.queue = queue
|
||||
self.frameReceived = frameReceived
|
||||
}
|
||||
|
||||
let renderContext = DrawingContext(size: pixelSize, scale: 1.0, clear: true)
|
||||
|
||||
instance.renderFrame(with: Int32(instance.frameCount - 1), into: renderContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(renderContext.size.width * renderContext.scale), height: Int32(renderContext.size.height * renderContext.scale), bytesPerRow: Int32(renderContext.bytesPerRow))
|
||||
|
||||
guard let image = renderContext.generateImage() else {
|
||||
return
|
||||
var isCancelled: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func add(with drawingBlock: (AnimationCacheItemDrawingSurface) -> Double?, proposedWidth: Int, proposedHeight: Int, insertKeyframe: Bool) {
|
||||
let renderContext = DrawingContext(size: CGSize(width: proposedWidth, height: proposedHeight), scale: 1.0, clear: true)
|
||||
let _ = drawingBlock(AnimationCacheItemDrawingSurface(
|
||||
argb: renderContext.bytes.assumingMemoryBound(to: UInt8.self),
|
||||
width: Int(renderContext.scaledSize.width),
|
||||
height: Int(renderContext.scaledSize.height),
|
||||
bytesPerRow: renderContext.bytesPerRow,
|
||||
length: renderContext.length
|
||||
))
|
||||
if let image = renderContext.generateImage() {
|
||||
self.frameReceived(image)
|
||||
}
|
||||
}
|
||||
|
||||
func finish() {
|
||||
}
|
||||
}
|
||||
let innerWriter = AnimationCacheItemWriterImpl(queue: queue, frameReceived: { image in
|
||||
guard let pngData = image.pngData() else {
|
||||
return
|
||||
}
|
||||
@ -50,6 +75,36 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
|
||||
let dataDisposable = fetchFrame(AnimationCacheFetchOptions(
|
||||
size: pixelSize,
|
||||
writer: innerWriter,
|
||||
firstFrameOnly: true
|
||||
))
|
||||
|
||||
/*let dataDisposable = context.account.postbox.mediaBox.resourceData(animation.resource).start(next: { data in
|
||||
if !data.complete {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||
return
|
||||
}
|
||||
guard let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
guard let instance = LottieInstance(data: unpackedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
instance.renderFrame(with: Int32(instance.frameCount - 1), into: renderContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(renderContext.size.width * renderContext.scale), height: Int32(renderContext.size.height * renderContext.scale), bytesPerRow: Int32(renderContext.bytesPerRow))
|
||||
|
||||
|
||||
})*/
|
||||
|
||||
return ActionDisposable {
|
||||
fetchDisposable.dispose()
|
||||
dataDisposable.dispose()
|
||||
@ -88,7 +143,7 @@ public final class ReactionImageNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.disposable = (reactionStaticImage(context: context, animation: animationFile, pixelSize: CGSize(width: displaySize.width * UIScreenScale, height: displaySize.height * UIScreenScale))
|
||||
self.disposable = (reactionStaticImage(context: context, animation: animationFile, pixelSize: CGSize(width: displaySize.width * UIScreenScale, height: displaySize.height * UIScreenScale), queue: sharedReactionStaticImage)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -1519,7 +1519,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
|
||||
if !items.reactionItems.isEmpty, let context = items.context {
|
||||
let reactionContextNode = ReactionContextNode(context: context, presentationData: self.presentationData, items: items.reactionItems, getEmojiContent: items.getEmojiContent, requestLayout: { _ in })
|
||||
let reactionContextNode = ReactionContextNode(context: context, presentationData: self.presentationData, items: items.reactionItems, getEmojiContent: items.getEmojiContent, isExpandedUpdated: { _ in })
|
||||
self.reactionContextNode = reactionContextNode
|
||||
self.addSubnode(reactionContextNode)
|
||||
|
||||
@ -1529,6 +1529,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
controller.reactionSelected?(reaction, isLarge)
|
||||
}
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
controller.premiumReactionsSelected?()
|
||||
}
|
||||
}
|
||||
|
||||
let previousActionsContainerNode = self.actionsContainerNode
|
||||
@ -2459,7 +2465,8 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
|
||||
private var shouldBeDismissedDisposable: Disposable?
|
||||
|
||||
public var reactionSelected: ((ReactionContextItem, Bool) -> Void)?
|
||||
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
||||
public var premiumReactionsSelected: (() -> Void)?
|
||||
|
||||
public var getOverlayViews: (() -> [UIView])?
|
||||
|
||||
|
@ -353,6 +353,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
private var proposedReactionsPositionLock: CGFloat?
|
||||
private var currentReactionsPositionLock: CGFloat?
|
||||
|
||||
private func setCurrentReactionsPositionLock() {
|
||||
self.currentReactionsPositionLock = self.proposedReactionsPositionLock
|
||||
}
|
||||
|
||||
private func getCurrentReactionsPositionLock() -> CGFloat? {
|
||||
return self.currentReactionsPositionLock
|
||||
}
|
||||
|
||||
func update(
|
||||
presentationData: PresentationData,
|
||||
layout: ContainerViewLayout,
|
||||
@ -433,10 +444,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
presentationData: presentationData,
|
||||
items: reactionItems.reactionItems,
|
||||
getEmojiContent: reactionItems.getEmojiContent,
|
||||
requestLayout: { [weak self] transition in
|
||||
isExpandedUpdated: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.setCurrentReactionsPositionLock()
|
||||
strongSelf.requestUpdate(transition)
|
||||
}
|
||||
)
|
||||
@ -453,11 +465,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
controller.reactionSelected?(reaction, isLarge)
|
||||
}
|
||||
reactionContextNode.premiumReactionsSelected = { [weak self] in
|
||||
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
|
||||
return
|
||||
}
|
||||
controller.premiumReactionsSelected?()
|
||||
}
|
||||
}
|
||||
contentTopInset += reactionContextNode.currentContentHeight + 8.0
|
||||
//if reactionContextNode.currentContentHeight > 100.0 {
|
||||
contentTopInset += 10.0
|
||||
//}
|
||||
contentTopInset += reactionContextNode.currentContentHeight + 18.0
|
||||
} else if let reactionContextNode = self.reactionContextNode {
|
||||
self.reactionContextNode = nil
|
||||
removedReactionContextNode = reactionContextNode
|
||||
@ -474,7 +489,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let contentParentGlobalFrame: CGRect
|
||||
var contentRect: CGRect
|
||||
|
||||
@ -555,7 +569,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
if case .animateOut = stateTransition {
|
||||
isAnimatingOut = true
|
||||
} else {
|
||||
if let topPositionLock = self.actionsStackNode.topPositionLock {
|
||||
if let currentReactionsPositionLock = self.currentReactionsPositionLock, let reactionContextNode = self.reactionContextNode {
|
||||
contentRect.origin.y = currentReactionsPositionLock + reactionContextNode.currentContentHeight + 18.0
|
||||
} else if let topPositionLock = self.actionsStackNode.topPositionLock {
|
||||
contentRect.origin.y = topPositionLock - contentActionsSpacing - contentRect.height
|
||||
} else if keepInPlace {
|
||||
} else {
|
||||
@ -581,7 +597,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
reactionContextNodeTransition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true)
|
||||
reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: contentRect.offsetBy(dx: contentParentGlobalFrame.minX, dy: 0.0), isAnimatingOut: isAnimatingOut, transition: reactionContextNodeTransition)
|
||||
|
||||
self.proposedReactionsPositionLock = contentRect.minY - 18.0 - reactionContextNode.currentContentHeight - 46.0
|
||||
} else {
|
||||
self.proposedReactionsPositionLock = nil
|
||||
}
|
||||
|
||||
if let _ = self.currentReactionsPositionLock {
|
||||
transition.updateAlpha(node: self.actionsStackNode, alpha: 0.0)
|
||||
} else {
|
||||
transition.updateAlpha(node: self.actionsStackNode, alpha: 1.0)
|
||||
}
|
||||
|
||||
if let removedReactionContextNode = removedReactionContextNode {
|
||||
removedReactionContextNode.animateOut(to: contentRect, animatingOutToReaction: false)
|
||||
transition.updateAlpha(node: removedReactionContextNode, alpha: 0.0, completion: { [weak removedReactionContextNode] _ in
|
||||
@ -645,7 +672,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
|
||||
let contentHeight: CGFloat
|
||||
if self.actionsStackNode.topPositionLock != nil {
|
||||
if self.actionsStackNode.topPositionLock != nil || self.currentReactionsPositionLock != nil {
|
||||
contentHeight = layout.size.height
|
||||
} else {
|
||||
if keepInPlace, case .extracted = self.source {
|
||||
@ -720,7 +747,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
currentContentScreenFrame = contentRect
|
||||
}
|
||||
|
||||
self.actionsStackNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05)
|
||||
self.actionsStackNode.layer.animateAlpha(from: 0.0, to: self.actionsStackNode.alpha, duration: 0.05)
|
||||
self.actionsStackNode.layer.animateSpring(
|
||||
from: 0.01 as NSNumber,
|
||||
to: 1.0 as NSNumber,
|
||||
@ -932,7 +959,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
)
|
||||
}
|
||||
|
||||
self.actionsStackNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||
self.actionsStackNode.layer.animateAlpha(from: self.actionsStackNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||
self.actionsStackNode.layer.animate(
|
||||
from: 1.0 as NSNumber,
|
||||
to: 0.01 as NSNumber,
|
||||
|
@ -39,7 +39,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
private let largeCircleSize: CGFloat
|
||||
private let smallCircleSize: CGFloat
|
||||
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private(set) var vibrancyEffectView: UIVisualEffectView?
|
||||
|
||||
private let maskLayer: SimpleLayer
|
||||
private let backgroundClippingLayer: SimpleLayer
|
||||
@ -56,11 +57,11 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
self.largeCircleSize = largeCircleSize
|
||||
self.smallCircleSize = smallCircleSize
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
|
||||
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
|
||||
self.maskLayer = SimpleLayer()
|
||||
self.backgroundClippingLayer = SimpleLayer()
|
||||
self.backgroundClippingLayer.cornerRadius = 52.0
|
||||
self.backgroundClippingLayer.cornerRadius = 47.0
|
||||
self.backgroundClippingLayer.masksToBounds = true
|
||||
self.backgroundMaskNode = maskNode
|
||||
|
||||
@ -79,7 +80,6 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// self.backgroundLayer.cornerCurve = .circular
|
||||
self.largeCircleLayer.cornerCurve = .circular
|
||||
self.smallCircleLayer.cornerCurve = .circular
|
||||
}
|
||||
@ -94,7 +94,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
self.largeCircleShadowLayer.opacity = 0.0
|
||||
self.smallCircleShadowLayer.opacity = 0.0
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.view.addSubview(self.backgroundView)
|
||||
|
||||
self.maskLayer.addSublayer(self.smallCircleLayer)
|
||||
self.maskLayer.addSublayer(self.largeCircleLayer)
|
||||
@ -102,7 +102,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
|
||||
self.backgroundClippingLayer.addSublayer(self.backgroundMaskNode.layer)
|
||||
|
||||
self.backgroundNode.layer.mask = self.maskLayer
|
||||
self.backgroundView.layer.mask = self.maskLayer
|
||||
}
|
||||
|
||||
func updateIsIntersectingContent(isIntersectingContent: Bool, transition: ContainedViewLayoutTransition) {
|
||||
@ -125,11 +125,28 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.updateColor(color: theme.contextMenu.backgroundColor, transition: .immediate)
|
||||
if theme.overallDarkAppearance {
|
||||
if let vibrancyEffectView = self.vibrancyEffectView {
|
||||
self.vibrancyEffectView = nil
|
||||
vibrancyEffectView.removeFromSuperview()
|
||||
}
|
||||
} else {
|
||||
if self.vibrancyEffectView == nil {
|
||||
let style: UIBlurEffect.Style
|
||||
style = .extraLight
|
||||
let blurEffect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
|
||||
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
|
||||
self.vibrancyEffectView = vibrancyEffectView
|
||||
self.backgroundView.addSubview(vibrancyEffectView)
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundView.updateColor(color: theme.contextMenu.backgroundColor, transition: .immediate)
|
||||
|
||||
let shadowColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
|
||||
if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: 52.0, shadowBlur: shadowInset) {
|
||||
if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: 46.0, shadowBlur: shadowInset) {
|
||||
ASDisplayNodeSetResizableContents(self.backgroundShadowLayer, image)
|
||||
}
|
||||
if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: self.largeCircleSize, shadowBlur: shadowInset) {
|
||||
@ -148,7 +165,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
backgroundMaskNodeFrame = backgroundMaskNodeFrame.offsetBy(dx: 0.0, dy: (updatedHeight - backgroundMaskNodeFrame.height) * 0.5)
|
||||
}
|
||||
|
||||
transition.updateCornerRadius(layer: self.backgroundClippingLayer, cornerRadius: 52.0 / 2.0)
|
||||
transition.updateCornerRadius(layer: self.backgroundClippingLayer, cornerRadius: 46.0 / 2.0)
|
||||
|
||||
let largeCircleFrame: CGRect
|
||||
let smallCircleFrame: CGRect
|
||||
@ -171,8 +188,12 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
transition.updateFrame(layer: self.largeCircleShadowLayer, frame: largeCircleFrame.insetBy(dx: -shadowInset, dy: -shadowInset), beginWithCurrentState: true)
|
||||
transition.updateFrame(layer: self.smallCircleShadowLayer, frame: smallCircleFrame.insetBy(dx: -shadowInset, dy: -shadowInset), beginWithCurrentState: true)
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: contentBounds, beginWithCurrentState: true)
|
||||
self.backgroundNode.update(size: contentBounds.size, transition: transition)
|
||||
transition.updateFrame(view: self.backgroundView, frame: contentBounds, beginWithCurrentState: true)
|
||||
self.backgroundView.update(size: contentBounds.size, transition: transition)
|
||||
|
||||
if let vibrancyEffectView = self.vibrancyEffectView {
|
||||
transition.updateFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: contentBounds.size))
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
@ -199,7 +220,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
let springDelay: Double = 0.05
|
||||
let shadowInset: CGFloat = 15.0
|
||||
|
||||
let contentBounds = self.backgroundNode.frame
|
||||
let contentBounds = self.backgroundView.frame
|
||||
|
||||
let visualSourceBackgroundFrame = sourceBackgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)
|
||||
let sourceShadowFrame = visualSourceBackgroundFrame.insetBy(dx: -shadowInset, dy: -shadowInset)
|
||||
|
@ -30,8 +30,8 @@ public final class ReactionItem {
|
||||
public let stillAnimation: TelegramMediaFile
|
||||
public let listAnimation: TelegramMediaFile
|
||||
public let largeListAnimation: TelegramMediaFile
|
||||
public let applicationAnimation: TelegramMediaFile
|
||||
public let largeApplicationAnimation: TelegramMediaFile
|
||||
public let applicationAnimation: TelegramMediaFile?
|
||||
public let largeApplicationAnimation: TelegramMediaFile?
|
||||
public let isCustom: Bool
|
||||
|
||||
public init(
|
||||
@ -40,8 +40,8 @@ public final class ReactionItem {
|
||||
stillAnimation: TelegramMediaFile,
|
||||
listAnimation: TelegramMediaFile,
|
||||
largeListAnimation: TelegramMediaFile,
|
||||
applicationAnimation: TelegramMediaFile,
|
||||
largeApplicationAnimation: TelegramMediaFile,
|
||||
applicationAnimation: TelegramMediaFile?,
|
||||
largeApplicationAnimation: TelegramMediaFile?,
|
||||
isCustom: Bool
|
||||
) {
|
||||
self.reaction = reaction
|
||||
@ -53,6 +53,15 @@ public final class ReactionItem {
|
||||
self.largeApplicationAnimation = largeApplicationAnimation
|
||||
self.isCustom = isCustom
|
||||
}
|
||||
|
||||
var updateMessageReaction: UpdateMessageReaction {
|
||||
switch self.reaction.rawValue {
|
||||
case let .builtin(value):
|
||||
return .builtin(value)
|
||||
case let .custom(fileId):
|
||||
return .custom(fileId: fileId, file: self.listAnimation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ReactionContextItem {
|
||||
@ -75,12 +84,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
private let items: [ReactionContextItem]
|
||||
private let originalItems: [ReactionContextItem]
|
||||
private let getEmojiContent: (() -> Signal<EmojiPagerContentComponent, NoError>)?
|
||||
private let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||
private let isExpandedUpdated: (ContainedViewLayoutTransition) -> Void
|
||||
|
||||
private let backgroundNode: ReactionContextBackgroundNode
|
||||
|
||||
private let contentTintContainer: ASDisplayNode
|
||||
private let contentContainer: ASDisplayNode
|
||||
private let contentContainerMask: UIImageView
|
||||
private let leftBackgroundMaskNode: ASDisplayNode
|
||||
@ -91,6 +100,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var visibleItemNodes: [Int: ReactionItemNode] = [:]
|
||||
private var visibleItemMaskNodes: [Int: ASDisplayNode] = [:]
|
||||
private let expandItemView: UIView
|
||||
private let expandItemVibrancyView: UIView
|
||||
private let expandItemArrowView: UIImageView
|
||||
|
||||
private var reactionSelectionComponentHost: ComponentView<Empty>?
|
||||
@ -104,10 +114,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var continuousHaptic: Any?
|
||||
private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
|
||||
private var isLeftAligned: Bool = true
|
||||
private var visibleItemCount: Int?
|
||||
|
||||
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
|
||||
|
||||
public var reactionSelected: ((ReactionContextItem, Bool) -> Void)?
|
||||
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
||||
public var premiumReactionsSelected: (() -> Void)?
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
private var standaloneReactionAnimation: StandaloneReactionAnimation?
|
||||
@ -117,15 +129,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private var didAnimateIn: Bool = false
|
||||
|
||||
public private(set) var currentContentHeight: CGFloat = 52.0
|
||||
public private(set) var currentContentHeight: CGFloat = 46.0
|
||||
public private(set) var isExpanded: Bool = false
|
||||
|
||||
public init(context: AccountContext, presentationData: PresentationData, items: [ReactionContextItem], getEmojiContent: (() -> Signal<EmojiPagerContentComponent, NoError>)?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
private var emojiContent: EmojiPagerContentComponent?
|
||||
private var emojiContentDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, presentationData: PresentationData, items: [ReactionContextItem], getEmojiContent: (() -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.items = Array(items.prefix(6))
|
||||
self.originalItems = items
|
||||
self.items = items
|
||||
self.getEmojiContent = getEmojiContent
|
||||
self.requestLayout = requestLayout
|
||||
self.isExpandedUpdated = isExpandedUpdated
|
||||
|
||||
self.backgroundMaskNode = ASDisplayNode()
|
||||
self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode)
|
||||
@ -155,8 +170,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.contentContainer.clipsToBounds = true
|
||||
self.contentContainer.addSubnode(self.scrollNode)
|
||||
|
||||
self.contentTintContainer = ASDisplayNode()
|
||||
self.contentTintContainer.clipsToBounds = true
|
||||
self.contentTintContainer.isUserInteractionEnabled = false
|
||||
|
||||
self.contentContainerMask = UIImageView()
|
||||
self.contentContainerMask.image = generateImage(CGSize(width: 52.0, height: 52.0), rotatedContext: { size, context in
|
||||
self.contentContainerMask.image = generateImage(CGSize(width: 46.0, height: 46.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: 1.1)
|
||||
@ -182,15 +201,16 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
context.setFillColor(shadowColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: gradientWidth - 1.0, dy: gradientWidth - 1.0))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(52.0 / 2.0), topCapHeight: Int(52.0 / 2.0))
|
||||
self.contentContainer.view.mask = self.contentContainerMask
|
||||
})?.stretchableImage(withLeftCapWidth: Int(46.0 / 2.0), topCapHeight: Int(46.0 / 2.0))
|
||||
//self.contentContainer.view.mask = self.contentContainerMask
|
||||
|
||||
self.expandItemView = UIView()
|
||||
self.expandItemVibrancyView = UIView()
|
||||
self.expandItemArrowView = UIImageView()
|
||||
self.expandItemArrowView.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)
|
||||
self.expandItemArrowView.transform = CGAffineTransform(rotationAngle: CGFloat.pi * 0.5)
|
||||
self.expandItemArrowView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReactionExpandArrow"), color: .white)
|
||||
self.expandItemView.addSubview(self.expandItemArrowView)
|
||||
self.scrollNode.view.addSubview(self.expandItemView)
|
||||
self.contentContainer.view.addSubview(self.expandItemView)
|
||||
self.contentTintContainer.view.addSubview(self.expandItemVibrancyView)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -202,6 +222,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.addSubnode(self.previewingItemContainer)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiContentDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -223,7 +247,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (backgroundFrame: CGRect, visualBackgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) {
|
||||
var contentSize = contentSize
|
||||
contentSize.width = max(52.0, contentSize.width)
|
||||
contentSize.width = max(46.0, contentSize.width)
|
||||
contentSize.height = self.currentContentHeight
|
||||
|
||||
let sideInset: CGFloat = 11.0
|
||||
@ -252,9 +276,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let cloudSourcePoint: CGFloat
|
||||
if isLeftAligned {
|
||||
cloudSourcePoint = min(rect.maxX - 52.0 / 2.0, anchorRect.maxX - 4.0)
|
||||
cloudSourcePoint = min(rect.maxX - 46.0 / 2.0, anchorRect.maxX - 4.0)
|
||||
} else {
|
||||
cloudSourcePoint = max(rect.minX + 52.0 / 2.0, anchorRect.minX)
|
||||
cloudSourcePoint = max(rect.minX + 46.0 / 2.0, anchorRect.minX)
|
||||
}
|
||||
|
||||
let visualRect = rect
|
||||
@ -267,11 +291,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ContainedViewLayoutTransition) {
|
||||
let sideInset: CGFloat = 11.0
|
||||
let itemSpacing: CGFloat = 9.0
|
||||
let itemSize: CGFloat = 40.0
|
||||
guard let visibleItemCount = self.visibleItemCount else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerHeight: CGFloat = 52.0
|
||||
let sideInset: CGFloat = 6.0
|
||||
let itemSpacing: CGFloat = 8.0
|
||||
let itemSize: CGFloat = 36.0
|
||||
|
||||
let containerHeight: CGFloat = 46.0
|
||||
var contentHeight: CGFloat = containerHeight
|
||||
if self.highlightedReaction != nil {
|
||||
contentHeight = floor(contentHeight * 0.9)
|
||||
@ -304,7 +332,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var validIndices = Set<Int>()
|
||||
var nextX: CGFloat = sideInset
|
||||
for i in 0 ..< self.items.count {
|
||||
for i in 0 ..< min(self.items.count, visibleItemCount - 1) {
|
||||
var currentItemSize = itemSize
|
||||
if let highlightedReactionIndex = highlightedReactionIndex {
|
||||
if highlightedReactionIndex == i {
|
||||
@ -398,11 +426,16 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let baseNextFrame = CGRect(origin: CGPoint(x: nextX, y: containerHeight - contentHeight + floor((contentHeight - itemSize) / 2.0)), size: CGSize(width: itemSize, height: itemSize)).insetBy(dx: 2.0, dy: 2.0)
|
||||
let baseNextFrame = CGRect(origin: CGPoint(x: nextX + 3.0, y: containerHeight - contentHeight + floor((contentHeight - 30.0) / 2.0) + (self.isExpanded ? 46.0 : 0.0)), size: CGSize(width: 30.0, height: 30.0))
|
||||
|
||||
transition.updateFrame(view: self.expandItemView, frame: baseNextFrame)
|
||||
self.expandItemView.layer.cornerRadius = baseNextFrame.width / 2.0
|
||||
self.expandItemView.frame = baseNextFrame
|
||||
|
||||
transition.updateFrame(view: self.expandItemVibrancyView, frame: baseNextFrame)
|
||||
self.expandItemVibrancyView.layer.cornerRadius = baseNextFrame.width / 2.0
|
||||
|
||||
if let image = self.expandItemArrowView.image {
|
||||
self.expandItemArrowView.frame = CGRect(origin: CGPoint(x: floor((baseNextFrame.width - image.size.width) / 2.0), y: floor((baseNextFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||
self.expandItemArrowView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((baseNextFrame.width - image.size.width) / 2.0), y: floorToScreenPixels((baseNextFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
if let currentMaskFrame = currentMaskFrame {
|
||||
@ -433,23 +466,29 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
private func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, isAnimatingOut: Bool, transition: ContainedViewLayoutTransition, animateInFromAnchorRect: CGRect?, animateOutToAnchorRect: CGRect?, animateReactionHighlight: Bool = false) {
|
||||
if isAnimatingOut {
|
||||
//self.currentContentHeight = 52.0
|
||||
}
|
||||
|
||||
self.expandItemView.backgroundColor = self.presentationData.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||
self.expandItemView.backgroundColor = self.presentationData.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.mixedWith(self.presentationData.theme.contextMenu.backgroundColor.withMultipliedAlpha(0.4), alpha: 0.5)
|
||||
self.expandItemVibrancyView.backgroundColor = .white
|
||||
|
||||
self.validLayout = (size, insets, anchorRect)
|
||||
|
||||
let sideInset: CGFloat = 11.0
|
||||
let itemSpacing: CGFloat = 9.0
|
||||
let itemSize: CGFloat = 40.0
|
||||
let externalSideInset: CGFloat = 4.0
|
||||
let sideInset: CGFloat = 6.0
|
||||
let itemSpacing: CGFloat = 8.0
|
||||
let itemSize: CGFloat = 36.0
|
||||
let verticalInset: CGFloat = 13.0
|
||||
let rowHeight: CGFloat = 30.0
|
||||
|
||||
let visibleItemCount = 7
|
||||
let minVisibleItemCount: CGFloat = CGFloat(visibleItemCount)
|
||||
let completeContentWidth = minVisibleItemCount * itemSize + (CGFloat(self.items.count) - 1.0) * itemSpacing + sideInset * 2.0
|
||||
let totalItemSlotCount = self.items.count + 1
|
||||
|
||||
var maxRowItemCount = Int(floor((size.width - sideInset * 2.0 - externalSideInset * 2.0 - itemSpacing) / (itemSize + itemSpacing)))
|
||||
maxRowItemCount = min(maxRowItemCount, 8)
|
||||
var itemCount = min(totalItemSlotCount, maxRowItemCount)
|
||||
if self.isExpanded {
|
||||
itemCount = maxRowItemCount
|
||||
}
|
||||
|
||||
let minVisibleItemCount: CGFloat = CGFloat(itemCount)
|
||||
let completeContentWidth = CGFloat(itemCount) * itemSize + (CGFloat(itemCount) - 1.0) * itemSpacing + sideInset * 2.0
|
||||
var visibleContentWidth = floor(minVisibleItemCount * itemSize + (minVisibleItemCount - 1.0) * itemSpacing + sideInset * 2.0)
|
||||
if visibleContentWidth > size.width - sideInset * 2.0 {
|
||||
visibleContentWidth = size.width - sideInset * 2.0
|
||||
@ -463,16 +502,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let (actualBackgroundFrame, visualBackgroundFrame, isLeftAligned, cloudSourcePoint) = self.calculateBackgroundFrame(containerSize: CGSize(width: size.width, height: size.height), insets: backgroundInsets, anchorRect: anchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight))
|
||||
self.isLeftAligned = isLeftAligned
|
||||
self.visibleItemCount = itemCount
|
||||
|
||||
transition.updateFrame(node: self.contentContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.contentTintContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: actualBackgroundFrame.size), beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? 46.0 : 0.0), size: actualBackgroundFrame.size), beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.previewingItemContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: visualBackgroundFrame.size.height)
|
||||
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
if (self.currentContentHeight > 100.0 || self.reactionSelectionComponentHost != nil), let getEmojiContent = self.getEmojiContent {
|
||||
if (self.isExpanded || self.reactionSelectionComponentHost != nil), let getEmojiContent = self.getEmojiContent {
|
||||
let reactionSelectionComponentHost: ComponentView<Empty>
|
||||
var componentTransition = Transition(transition)
|
||||
if let current = self.reactionSelectionComponentHost {
|
||||
@ -483,111 +524,154 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.reactionSelectionComponentHost = reactionSelectionComponentHost
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
var emojiContent: EmojiPagerContentComponent?
|
||||
let _ = (getEmojiContent() |> take(1)).start(next: { value in
|
||||
emojiContent = value
|
||||
semaphore.signal()
|
||||
})
|
||||
|
||||
semaphore.wait()
|
||||
|
||||
emojiContent!.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer in
|
||||
guard let strongSelf = self, let itemFile = item.itemFile else {
|
||||
if let current = self.emojiContent {
|
||||
emojiContent = current
|
||||
} else {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let _ = (getEmojiContent() |> take(1)).start(next: { value in
|
||||
emojiContent = value
|
||||
semaphore.signal()
|
||||
})
|
||||
|
||||
semaphore.wait()
|
||||
self.emojiContent = emojiContent
|
||||
|
||||
self.emojiContentDisposable = (getEmojiContent()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var found = false
|
||||
if let groupId = groupId.base as? String, groupId == "recent" {
|
||||
for reactionItem in strongSelf.originalItems {
|
||||
if case let .reaction(reactionItem) = reactionItem {
|
||||
if reactionItem.stillAnimation.fileId == itemFile.fileId {
|
||||
found = true
|
||||
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(.reaction(reactionItem), false)
|
||||
|
||||
break
|
||||
strongSelf.emojiContent = emojiContent
|
||||
if let (size, insets, anchorRect) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, insets: insets, anchorRect: anchorRect, isAnimatingOut: false, transition: .immediate, animateInFromAnchorRect: nil, animateOutToAnchorRect: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if let emojiContent = emojiContent {
|
||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer in
|
||||
guard let strongSelf = self, let itemFile = item.itemFile else {
|
||||
return
|
||||
}
|
||||
var found = false
|
||||
if let groupId = groupId.base as? String, groupId == "recent" {
|
||||
for reactionItem in strongSelf.items {
|
||||
if case let .reaction(reactionItem) = reactionItem {
|
||||
if reactionItem.stillAnimation.fileId == itemFile.fileId {
|
||||
found = true
|
||||
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, false)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
let reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
|
||||
appearAnimation: itemFile,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: itemFile,
|
||||
largeListAnimation: itemFile,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
)
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, false)
|
||||
}
|
||||
},
|
||||
deleteBackwards: {
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
addGroupAction: { _, _ in
|
||||
},
|
||||
clearGroup: { _ in
|
||||
},
|
||||
pushController: { _ in
|
||||
},
|
||||
presentController: { _ in
|
||||
},
|
||||
presentGlobalOverlayController: { _ in
|
||||
},
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
customLayout: EmojiPagerContentComponent.CustomLayout(
|
||||
itemsPerRow: itemCount,
|
||||
itemSize: itemSize,
|
||||
sideInset: sideInset,
|
||||
itemSpacing: itemSpacing
|
||||
),
|
||||
externalBackground: EmojiPagerContentComponent.ExternalBackground(
|
||||
effectContainerView: self.backgroundNode.vibrancyEffectView?.contentView
|
||||
)
|
||||
)
|
||||
|
||||
let _ = reactionSelectionComponentHost.update(
|
||||
transition: componentTransition,
|
||||
component: AnyComponent(EmojiStatusSelectionComponent(
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
deviceMetrics: DeviceMetrics.iPhone13,
|
||||
emojiContent: emojiContent,
|
||||
backgroundColor: .clear,
|
||||
separatorColor: self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: actualBackgroundFrame.width, height: 300.0)
|
||||
)
|
||||
if let componentView = reactionSelectionComponentHost.view {
|
||||
var animateIn = false
|
||||
if componentView.superview == nil {
|
||||
componentView.layer.cornerRadius = 26.0
|
||||
componentView.clipsToBounds = true
|
||||
|
||||
self.contentContainer.view.insertSubview(componentView, belowSubview: self.scrollNode.view)
|
||||
self.contentContainer.view.mask = nil
|
||||
for (_, itemNode) in self.visibleItemNodes {
|
||||
itemNode.isHidden = true
|
||||
}
|
||||
if let emojiView = reactionSelectionComponentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
|
||||
emojiView.animateInReactionSelection(stationaryItemCount: itemCount - 1)
|
||||
|
||||
if let mirrorContentClippingView = emojiView.mirrorContentClippingView {
|
||||
Transition(transition).animateBoundsOrigin(view: mirrorContentClippingView, from: CGPoint(x: 0.0, y: 46.0), to: CGPoint(), additive: true)
|
||||
}
|
||||
}
|
||||
self.expandItemView.alpha = 0.0
|
||||
self.expandItemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.scrollNode.isHidden = true
|
||||
})
|
||||
self.expandItemView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.expandItemVibrancyView.alpha = 0.0
|
||||
self.expandItemVibrancyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
self.expandItemVibrancyView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
animateIn = true
|
||||
}
|
||||
if !found, case let .reaction(sampleItem) = strongSelf.originalItems.first {
|
||||
let reactionItem = ReactionItem(
|
||||
reaction: sampleItem.reaction,
|
||||
appearAnimation: sampleItem.appearAnimation,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: sampleItem.listAnimation,
|
||||
largeListAnimation: sampleItem.largeListAnimation,
|
||||
applicationAnimation: sampleItem.applicationAnimation,
|
||||
largeApplicationAnimation: sampleItem.largeApplicationAnimation,
|
||||
isCustom: true
|
||||
)
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(.reaction(reactionItem), false)
|
||||
}
|
||||
},
|
||||
deleteBackwards: {
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
addGroupAction: { _, _ in
|
||||
},
|
||||
clearGroup: { _ in
|
||||
},
|
||||
pushController: { _ in
|
||||
},
|
||||
presentController: { _ in
|
||||
},
|
||||
presentGlobalOverlayController: { _ in
|
||||
},
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil
|
||||
)
|
||||
|
||||
let _ = reactionSelectionComponentHost.update(
|
||||
transition: componentTransition,
|
||||
component: AnyComponent(EmojiStatusSelectionComponent(
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
deviceMetrics: DeviceMetrics.iPhone13,
|
||||
emojiContent: emojiContent!,
|
||||
backgroundColor: .clear,
|
||||
separatorColor: self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: actualBackgroundFrame.width, height: 300.0)
|
||||
)
|
||||
if let componentView = reactionSelectionComponentHost.view {
|
||||
var animateIn = false
|
||||
if componentView.superview == nil {
|
||||
componentView.layer.cornerRadius = 26.0
|
||||
componentView.clipsToBounds = true
|
||||
|
||||
self.contentContainer.view.insertSubview(componentView, belowSubview: self.scrollNode.view)
|
||||
self.contentContainer.view.mask = nil
|
||||
self.scrollNode.alpha = 0.0
|
||||
self.scrollNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animateIn = true
|
||||
}
|
||||
|
||||
let componentFrame = CGRect(origin: CGPoint(), size: actualBackgroundFrame.size)
|
||||
|
||||
componentTransition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
|
||||
if animateIn {
|
||||
let componentFrame = CGRect(origin: CGPoint(), size: actualBackgroundFrame.size)
|
||||
|
||||
componentTransition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
|
||||
if animateIn {
|
||||
transition.animatePositionAdditive(layer: componentView.layer, offset: CGPoint(x: 0.0, y: -46.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||
@ -600,6 +684,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
transition: transition
|
||||
)
|
||||
|
||||
if let vibrancyEffectView = self.backgroundNode.vibrancyEffectView {
|
||||
if self.contentTintContainer.view.superview !== vibrancyEffectView.contentView {
|
||||
vibrancyEffectView.contentView.addSubview(self.contentTintContainer.view)
|
||||
}
|
||||
}
|
||||
|
||||
if let animateInFromAnchorRect = animateInFromAnchorRect {
|
||||
let springDuration: Double = 0.3
|
||||
let springDamping: CGFloat = 104.0
|
||||
@ -611,6 +701,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - visualBackgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
self.contentTintContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - visualBackgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.contentTintContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
|
||||
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight)).0
|
||||
|
||||
@ -644,6 +737,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode?.appear(animated: true)
|
||||
})
|
||||
}
|
||||
|
||||
let itemDelay = mainCircleDelay + Double(self.visibleItemNodes.count) * 0.06
|
||||
self.expandItemView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, delay: itemDelay)
|
||||
self.expandItemVibrancyView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, delay: itemDelay)
|
||||
} else {
|
||||
for i in 0 ..< self.items.count {
|
||||
guard let itemNode = self.visibleItemNodes[i] else {
|
||||
@ -745,8 +842,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let isStillImage: Bool
|
||||
if let customReactionSource = self.customReactionSource {
|
||||
let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item)
|
||||
if customReactionSource.item.listAnimation.isVideoEmoji || customReactionSource.item.listAnimation.isVideoSticker || customReactionSource.item.listAnimation.isAnimatedSticker {
|
||||
isStillImage = false
|
||||
} else {
|
||||
isStillImage = true
|
||||
}
|
||||
|
||||
let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, useDirectRendering: false)
|
||||
if let contents = customReactionSource.layer.contents {
|
||||
itemNode.setCustomContents(contents: contents)
|
||||
}
|
||||
@ -755,6 +859,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode.updateLayout(size: itemNode.frame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: .immediate)
|
||||
customReactionSource.layer.isHidden = true
|
||||
foundItemNode = itemNode
|
||||
} else {
|
||||
isStillImage = false
|
||||
}
|
||||
|
||||
guard let itemNode = foundItemNode else {
|
||||
@ -796,11 +902,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
||||
} else {
|
||||
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||
if itemNode.item.isCustom {
|
||||
//effectFrame = expandedFrame.insetBy(dx: expandedSize.width * 0.25, dy: expandedSize.width * 0.25)
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||
@ -810,22 +911,31 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: self.didTriggerExpandedReaction, isPreviewing: false, transition: transition)
|
||||
|
||||
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
|
||||
|
||||
let additionalAnimation: TelegramMediaFile
|
||||
if self.didTriggerExpandedReaction {
|
||||
let additionalAnimation: TelegramMediaFile?
|
||||
if self.didTriggerExpandedReaction, !isStillImage {
|
||||
additionalAnimation = itemNode.item.largeApplicationAnimation
|
||||
if incomingMessage {
|
||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
} else {
|
||||
additionalAnimation = itemNode.item.applicationAnimation
|
||||
}
|
||||
|
||||
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
|
||||
additionalAnimationNode.frame = effectFrame
|
||||
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
if let additionalAnimation = additionalAnimation {
|
||||
let additionalAnimationNodeValue = DefaultAnimatedStickerNodeImpl()
|
||||
additionalAnimationNode = additionalAnimationNodeValue
|
||||
if self.didTriggerExpandedReaction {
|
||||
if incomingMessage {
|
||||
additionalAnimationNodeValue.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
|
||||
additionalAnimationNodeValue.frame = effectFrame
|
||||
additionalAnimationNodeValue.updateLayout(size: effectFrame.size)
|
||||
self.addSubnode(additionalAnimationNodeValue)
|
||||
} else {
|
||||
additionalAnimationNode = nil
|
||||
}
|
||||
|
||||
var mainAnimationCompleted = false
|
||||
var additionalAnimationCompleted = false
|
||||
@ -835,13 +945,17 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
additionalAnimationNode.completed = { _ in
|
||||
if let additionalAnimationNode = additionalAnimationNode {
|
||||
additionalAnimationNode.completed = { _ in
|
||||
additionalAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
}
|
||||
} else {
|
||||
additionalAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
}
|
||||
|
||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak self, weak itemNode, weak targetView, weak animateTargetContainer] _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
|
||||
let afterCompletion: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -859,19 +973,35 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
targetView.isHidden = false
|
||||
targetView.alpha = 1.0
|
||||
targetView.imageView.alpha = 0.0
|
||||
targetView.addSubnode(itemNode)
|
||||
itemNode.frame = targetView.bounds
|
||||
|
||||
if isStillImage {
|
||||
targetView.imageView.alpha = 1.0
|
||||
} else {
|
||||
targetView.imageView.alpha = 0.0
|
||||
targetView.addSubnode(itemNode)
|
||||
itemNode.frame = targetView.bounds
|
||||
}
|
||||
|
||||
if strongSelf.hapticFeedback == nil {
|
||||
strongSelf.hapticFeedback = HapticFeedback()
|
||||
}
|
||||
strongSelf.hapticFeedback?.tap()
|
||||
})
|
||||
|
||||
if isStillImage {
|
||||
mainAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
if isStillImage {
|
||||
afterCompletion()
|
||||
} else {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: afterCompletion)
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
|
||||
additionalAnimationNode.visibility = true
|
||||
additionalAnimationNode?.visibility = true
|
||||
if let animateTargetContainer = animateTargetContainer {
|
||||
animateTargetContainer.isHidden = false
|
||||
animateTargetContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
@ -879,45 +1009,47 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||
if self.didTriggerExpandedReaction {
|
||||
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
||||
if let strongSelf = self, strongSelf.didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||
if !isStillImage {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||
if self.didTriggerExpandedReaction {
|
||||
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
||||
if let strongSelf = self, strongSelf.didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||
|
||||
addStandaloneReactionAnimation(standaloneReactionAnimation)
|
||||
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: strongSelf.context,
|
||||
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
reaction: itemNode.item,
|
||||
avatarPeers: [],
|
||||
playHaptic: false,
|
||||
isLarge: false,
|
||||
targetView: targetView,
|
||||
addStandaloneReactionAnimation: nil,
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.removeFromSupernode()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
addStandaloneReactionAnimation(standaloneReactionAnimation)
|
||||
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: strongSelf.context,
|
||||
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
reaction: itemNode.item,
|
||||
avatarPeers: [],
|
||||
playHaptic: false,
|
||||
isLarge: false,
|
||||
targetView: targetView,
|
||||
addStandaloneReactionAnimation: nil,
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.removeFromSupernode()
|
||||
}
|
||||
)
|
||||
mainAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
} else {
|
||||
if hideNode {
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
if let targetView = targetView as? ReactionIconView {
|
||||
targetView.imageView.alpha = 1.0
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
mainAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
} else {
|
||||
if hideNode {
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
if let targetView = targetView as? ReactionIconView {
|
||||
targetView.imageView.alpha = 1.0
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
mainAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
@ -995,9 +1127,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if self.expandItemView.bounds.contains(self.view.convert(point, to: self.expandItemView)) {
|
||||
self.currentContentHeight = 300.0
|
||||
self.requestLayout(.animated(duration: 0.4, curve: .spring))
|
||||
self.isExpanded = true
|
||||
self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring))
|
||||
} else if let reaction = self.reaction(at: point) {
|
||||
self.reactionSelected?(reaction, false)
|
||||
switch reaction {
|
||||
case let .reaction(reactionItem):
|
||||
self.reactionSelected?(reactionItem.updateMessageReaction, false)
|
||||
case .premium:
|
||||
self.premiumReactionsSelected?()
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -1101,7 +1239,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public func performReactionSelection(reaction: ReactionItem.Reaction, isLarge: Bool) {
|
||||
for (_, itemNode) in self.visibleItemNodes {
|
||||
if let itemNode = itemNode as? ReactionNode, itemNode.item.reaction == reaction {
|
||||
self.reactionSelected?(.reaction(itemNode.item), isLarge)
|
||||
self.reactionSelected?(itemNode.item.updateMessageReaction, isLarge)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -1227,35 +1365,45 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
|
||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate)
|
||||
|
||||
let additionalAnimationNode: AnimatedStickerNode
|
||||
if self.useDirectRendering {
|
||||
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||
} else {
|
||||
additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
}
|
||||
|
||||
let additionalAnimation: TelegramMediaFile
|
||||
let additionalAnimation: TelegramMediaFile?
|
||||
if isLarge && !forceSmallEffectAnimation {
|
||||
additionalAnimation = itemNode.item.largeApplicationAnimation
|
||||
if incomingMessage {
|
||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
} else {
|
||||
additionalAnimation = itemNode.item.applicationAnimation
|
||||
}
|
||||
|
||||
var additionalCachePathPrefix: String?
|
||||
additionalCachePathPrefix = itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)
|
||||
//#if DEBUG
|
||||
additionalCachePathPrefix = nil
|
||||
//#endif
|
||||
let additionalAnimationNode: AnimatedStickerNode?
|
||||
|
||||
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 1.33), height: Int(effectFrame.height * 1.33), playbackMode: .once, mode: .direct(cachePathPrefix: additionalCachePathPrefix))
|
||||
additionalAnimationNode.frame = effectFrame
|
||||
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
if let additionalAnimation = additionalAnimation {
|
||||
let additionalAnimationNodeValue: AnimatedStickerNode
|
||||
if self.useDirectRendering {
|
||||
additionalAnimationNodeValue = DirectAnimatedStickerNode()
|
||||
} else {
|
||||
additionalAnimationNodeValue = DefaultAnimatedStickerNodeImpl()
|
||||
}
|
||||
additionalAnimationNode = additionalAnimationNodeValue
|
||||
|
||||
if isLarge && !forceSmallEffectAnimation {
|
||||
if incomingMessage {
|
||||
additionalAnimationNodeValue.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
var additionalCachePathPrefix: String?
|
||||
additionalCachePathPrefix = itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)
|
||||
//#if DEBUG
|
||||
additionalCachePathPrefix = nil
|
||||
//#endif
|
||||
|
||||
additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 1.33), height: Int(effectFrame.height * 1.33), playbackMode: .once, mode: .direct(cachePathPrefix: additionalCachePathPrefix))
|
||||
additionalAnimationNodeValue.frame = effectFrame
|
||||
additionalAnimationNodeValue.updateLayout(size: effectFrame.size)
|
||||
self.addSubnode(additionalAnimationNodeValue)
|
||||
} else {
|
||||
additionalAnimationNode = nil
|
||||
}
|
||||
|
||||
if !isLarge, !avatarPeers.isEmpty, let url = getAppBundle().url(forResource: "effectavatar", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
if let additionalAnimationNode = additionalAnimationNode, !isLarge, !avatarPeers.isEmpty, let url = getAppBundle().url(forResource: "effectavatar", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||
view.animationSpeed = 1.0
|
||||
view.backgroundColor = nil
|
||||
@ -1387,20 +1535,24 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
additionalAnimationNode.completed = { [weak additionalAnimationNode] _ in
|
||||
additionalAnimationNode?.alpha = 0.0
|
||||
additionalAnimationNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
additionalAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
if forceSmallEffectAnimation {
|
||||
maybeBeginDismissAnimation()
|
||||
} else {
|
||||
beginDismissAnimation()
|
||||
if let additionalAnimationNode = additionalAnimationNode {
|
||||
additionalAnimationNode.completed = { [weak additionalAnimationNode] _ in
|
||||
additionalAnimationNode?.alpha = 0.0
|
||||
additionalAnimationNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
additionalAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
if forceSmallEffectAnimation {
|
||||
maybeBeginDismissAnimation()
|
||||
} else {
|
||||
beginDismissAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
additionalAnimationNode.visibility = true
|
||||
} else {
|
||||
additionalAnimationCompleted = true
|
||||
}
|
||||
|
||||
additionalAnimationNode.visibility = true
|
||||
|
||||
if !forceSmallEffectAnimation {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: {
|
||||
beginDismissAnimation()
|
||||
|
@ -104,7 +104,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.appearAnimation.resource)).start()
|
||||
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.stillAnimation.resource)).start()
|
||||
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.listAnimation.resource)).start()
|
||||
self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.applicationAnimation.resource)).start()
|
||||
if let applicationAnimation = item.applicationAnimation {
|
||||
self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: applicationAnimation.resource)).start()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -175,9 +177,12 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
}
|
||||
|
||||
if largeExpanded {
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isVideoEmoji)
|
||||
|
||||
animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
} else {
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource, isVideo: self.item.listAnimation.isVideoSticker || self.item.listAnimation.isVideoEmoji)
|
||||
animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
||||
}
|
||||
animationNode.frame = expandedAnimationFrame
|
||||
animationNode.updateLayout(size: expandedAnimationFrame.size)
|
||||
@ -203,16 +208,6 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
animateInAnimationNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
/*if let customContentsNode = self.customContentsNode {
|
||||
customContentsNode.alpha = 0.0
|
||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
guard let strongSelf = self, let customContentsNode = strongSelf.customContentsNode else {
|
||||
return
|
||||
}
|
||||
strongSelf.customContentsNode = nil
|
||||
customContentsNode.removeFromSupernode()
|
||||
})
|
||||
}*/
|
||||
|
||||
var referenceNode: ASDisplayNode?
|
||||
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||
@ -234,13 +229,14 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.staticAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
}
|
||||
|
||||
|
||||
if let customContentsNode = self.customContentsNode, !customContentsNode.isHidden {
|
||||
transition.animateTransformScale(node: customContentsNode, from: customContentsNode.bounds.width / animationFrame.width)
|
||||
transition.animatePositionAdditive(node: customContentsNode, offset: CGPoint(x: customContentsNode.frame.midX - animationFrame.midX, y: customContentsNode.frame.midY - animationFrame.midY))
|
||||
|
||||
customContentsNode.alpha = 0.0
|
||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
if self.item.listAnimation.isVideoEmoji || self.item.listAnimation.isVideoSticker || self.item.listAnimation.isAnimatedSticker {
|
||||
customContentsNode.alpha = 0.0
|
||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
@ -352,10 +348,6 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
|
||||
if let customContentsNode = self.customContentsNode {
|
||||
transition.updateFrame(node: customContentsNode, frame: animationFrame)
|
||||
/*if customContentsNode.alpha != 0.0 {
|
||||
customContentsNode.alpha = 0.0
|
||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ public func quickReactionSetupController(
|
||||
continue
|
||||
}
|
||||
if let centerAnimation = availableReaction.centerAnimation {
|
||||
let signal: Signal<(MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0 * 2.0, height: 72.0 * 2.0))
|
||||
let signal: Signal<(MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0 * 2.0, height: 72.0 * 2.0), queue: sharedReactionStaticImage)
|
||||
|> map { data -> (MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?) in
|
||||
guard data.isComplete else {
|
||||
return (availableReaction.value, nil)
|
||||
|
@ -1175,6 +1175,8 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedGreetingStickers(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedPremiumStickers(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedAllPremiumStickers(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedRecentStatusEmoji(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedFeaturedStatusEmoji(postbox: self.postbox, network: self.network).start())
|
||||
|
||||
if !supplementary {
|
||||
let mediaBox = postbox.mediaBox
|
||||
|
@ -202,3 +202,63 @@ func managedAllPremiumStickers(postbox: Postbox, network: Network) -> Signal<Voi
|
||||
})
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
||||
func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStatusEmoji, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
||||
return network.request(Api.functions.account.getEmojiStatuses(flags: 1 << 1, hash: hash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||
switch result {
|
||||
case .emojiStatusesNotModified:
|
||||
return .single(nil)
|
||||
case let .emojiStatuses(_, statuses):
|
||||
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
|
||||
|
||||
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.map(\.fileId))
|
||||
|> map { files -> [OrderedItemListEntry] in
|
||||
var items: [OrderedItemListEntry] = []
|
||||
for status in parsedStatuses {
|
||||
guard let file = files[status.fileId] else {
|
||||
continue
|
||||
}
|
||||
if let entry = CodableEntry(RecentMediaItem(file)) {
|
||||
items.append(OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: entry))
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
||||
func managedFeaturedStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudFeaturedStatusEmoji, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
||||
return network.request(Api.functions.account.getEmojiStatuses(flags: 1 << 0, hash: hash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||
switch result {
|
||||
case .emojiStatusesNotModified:
|
||||
return .single(nil)
|
||||
case let .emojiStatuses(_, statuses):
|
||||
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
|
||||
|
||||
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.map(\.fileId))
|
||||
|> map { files -> [OrderedItemListEntry] in
|
||||
var items: [OrderedItemListEntry] = []
|
||||
for status in parsedStatuses {
|
||||
guard let file = files[status.fileId] else {
|
||||
continue
|
||||
}
|
||||
if let entry = CodableEntry(RecentMediaItem(file)) {
|
||||
items.append(OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: entry))
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
@ -4,8 +4,36 @@ import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: MessageReaction.Reaction?, isLarge: Bool) -> Signal<Never, NoError> {
|
||||
public enum UpdateMessageReaction {
|
||||
case builtin(String)
|
||||
case custom(fileId: Int64, file: TelegramMediaFile?)
|
||||
|
||||
public var reaction: MessageReaction.Reaction {
|
||||
switch self {
|
||||
case let .builtin(value):
|
||||
return .builtin(value)
|
||||
case let .custom(fileId, _):
|
||||
return .custom(fileId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: UpdateMessageReaction?, isLarge: Bool) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
let mappedReaction: MessageReaction.Reaction?
|
||||
|
||||
switch reaction {
|
||||
case .none:
|
||||
mappedReaction = nil
|
||||
case let .custom(fileId, file):
|
||||
mappedReaction = .custom(fileId)
|
||||
if let file = file {
|
||||
transaction.storeMediaIfNotPresent(media: file)
|
||||
}
|
||||
case let .builtin(value):
|
||||
mappedReaction = .builtin(value)
|
||||
}
|
||||
|
||||
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
@ -19,7 +47,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
||||
break loop
|
||||
}
|
||||
}
|
||||
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: reaction, isLarge: isLarge))
|
||||
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: mappedReaction, isLarge: isLarge))
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
@ -68,6 +68,8 @@ public struct Namespaces {
|
||||
public static let LocalRecentEmoji: Int32 = 14
|
||||
public static let CloudFeaturedEmojiPacks: Int32 = 15
|
||||
public static let CloudAllPremiumStickers: Int32 = 16
|
||||
public static let CloudRecentStatusEmoji: Int32 = 17
|
||||
public static let CloudFeaturedStatusEmoji: Int32 = 18
|
||||
}
|
||||
|
||||
public struct CachedItemCollection {
|
||||
|
@ -165,47 +165,51 @@ public extension TelegramEngine {
|
||||
}
|
||||
|
||||
public func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> {
|
||||
return self.account.postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var cachedFiles: [Int64: TelegramMediaFile] = [:]
|
||||
for fileId in fileIds {
|
||||
if let file = transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile {
|
||||
cachedFiles[fileId] = file
|
||||
}
|
||||
}
|
||||
return cachedFiles
|
||||
return _internal_resolveInlineStickers(postbox: self.account.postbox, network: self.account.network, fileIds: fileIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_resolveInlineStickers(postbox: Postbox, network: Network, fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> {
|
||||
return postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var cachedFiles: [Int64: TelegramMediaFile] = [:]
|
||||
for fileId in fileIds {
|
||||
if let file = transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile {
|
||||
cachedFiles[fileId] = file
|
||||
}
|
||||
|> mapToSignal { cachedFiles -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
if cachedFiles.count == fileIds.count {
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
|
||||
var unknownIds = Set<Int64>()
|
||||
for fileId in fileIds {
|
||||
if cachedFiles[fileId] == nil {
|
||||
unknownIds.insert(fileId)
|
||||
}
|
||||
}
|
||||
|
||||
return self.account.network.request(Api.functions.messages.getCustomEmojiDocuments(documentId: Array(unknownIds)))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<[Api.Document]?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
guard let result = result else {
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
return self.account.postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var resultFiles: [Int64: TelegramMediaFile] = cachedFiles
|
||||
for document in result {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
resultFiles[file.fileId.id] = file
|
||||
transaction.storeMediaIfNotPresent(media: file)
|
||||
}
|
||||
}
|
||||
return resultFiles
|
||||
}
|
||||
return cachedFiles
|
||||
}
|
||||
|> mapToSignal { cachedFiles -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
if cachedFiles.count == fileIds.count {
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
|
||||
var unknownIds = Set<Int64>()
|
||||
for fileId in fileIds {
|
||||
if cachedFiles[fileId] == nil {
|
||||
unknownIds.insert(fileId)
|
||||
}
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.getCustomEmojiDocuments(documentId: Array(unknownIds)))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<[Api.Document]?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
guard let result = result else {
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
return postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var resultFiles: [Int64: TelegramMediaFile] = cachedFiles
|
||||
for document in result {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
resultFiles[file.fileId.id] = file
|
||||
transaction.storeMediaIfNotPresent(media: file)
|
||||
}
|
||||
}
|
||||
return resultFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -863,7 +863,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||
panelIconColor: UIColor(rgb: 0x858e99),
|
||||
panelHighlightedIconBackgroundColor: UIColor(rgb: 0x858e99, alpha: 0.2),
|
||||
panelHighlightedIconColor: UIColor(rgb: 0x4D5561),
|
||||
panelContentVibrantOverlayColor: day ? UIColor(white: 0.0, alpha: 0.3) : UIColor(white: 0.65, alpha: 0.65),
|
||||
panelContentVibrantOverlayColor: day ? UIColor(white: 0.0, alpha: 0.3) : UIColor(white: 0.7, alpha: 0.65),
|
||||
stickersBackgroundColor: UIColor(rgb: 0xe8ebf0),
|
||||
stickersSectionTextColor: UIColor(rgb: 0x9099a2),
|
||||
stickersSearchBackgroundColor: UIColor(rgb: 0xd9dbe1),
|
||||
|
@ -74,7 +74,7 @@ public struct AnimationCacheItemDrawingSurface {
|
||||
public let bytesPerRow: Int
|
||||
public let length: Int
|
||||
|
||||
init(
|
||||
public init(
|
||||
argb: UnsafeMutablePointer<UInt8>,
|
||||
width: Int,
|
||||
height: Int,
|
||||
@ -234,7 +234,10 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
var duration: Double
|
||||
}
|
||||
|
||||
let queue: Queue
|
||||
var queue: Queue {
|
||||
return self.innerQueue
|
||||
}
|
||||
let innerQueue: Queue
|
||||
var isCancelled: Bool = false
|
||||
|
||||
private let compressedPath: String
|
||||
@ -269,7 +272,7 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
self.dctQualityChroma = 88
|
||||
self.dctQualityDelta = 22
|
||||
|
||||
self.queue = queue
|
||||
self.innerQueue = queue
|
||||
self.compressedPath = allocateTempFile()
|
||||
|
||||
guard let file = ManagedFile(queue: nil, path: self.compressedPath, mode: .readwrite) else {
|
||||
|
@ -96,12 +96,14 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
|
||||
self.component = component
|
||||
|
||||
let topPanelHeight: CGFloat = 42.0
|
||||
|
||||
let keyboardSize = self.keyboardView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
containerInsets: UIEdgeInsets(top: 41.0 - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
|
||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
|
||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
||||
emojiContent: component.emojiContent,
|
||||
stickerContent: nil,
|
||||
@ -134,15 +136,15 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
self.keyboardClippingView.clipsToBounds = false
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: availableSize.width, height: availableSize.height - 41.0)))
|
||||
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight)))
|
||||
|
||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -41.0), size: keyboardSize))
|
||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
|
||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||
|
||||
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: 41.0)))
|
||||
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight)))
|
||||
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
@ -193,24 +195,24 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
|
||||
self.componentHost = ComponentView<Empty>()
|
||||
self.componentShadowLayer = SimpleLayer()
|
||||
self.componentShadowLayer.shadowOpacity = 0.15
|
||||
self.componentShadowLayer.shadowOpacity = 0.12
|
||||
self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.componentShadowLayer.shadowRadius = 15.0
|
||||
self.componentShadowLayer.shadowRadius = 16.0
|
||||
|
||||
self.cloudLayer0 = SimpleLayer()
|
||||
self.cloudShadowLayer0 = SimpleLayer()
|
||||
self.cloudShadowLayer0.shadowOpacity = 0.15
|
||||
self.cloudShadowLayer0.shadowOpacity = 0.12
|
||||
self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.cloudShadowLayer0.shadowRadius = 15.0
|
||||
self.cloudShadowLayer0.shadowRadius = 16.0
|
||||
|
||||
self.cloudLayer1 = SimpleLayer()
|
||||
self.cloudShadowLayer1 = SimpleLayer()
|
||||
self.cloudShadowLayer1.shadowOpacity = 0.15
|
||||
self.cloudShadowLayer1.shadowOpacity = 0.12
|
||||
self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.cloudShadowLayer1.shadowRadius = 15.0
|
||||
self.cloudShadowLayer1.shadowRadius = 16.0
|
||||
|
||||
super.init()
|
||||
|
||||
@ -258,7 +260,9 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
externalBackground: nil
|
||||
)
|
||||
|
||||
strongSelf.refreshLayout(transition: .immediate)
|
||||
@ -295,8 +299,24 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
self.cloudLayer0.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.cgColor
|
||||
self.cloudLayer1.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.cgColor
|
||||
let listBackgroundColor: UIColor
|
||||
let separatorColor: UIColor
|
||||
if self.presentationData.theme.overallDarkAppearance {
|
||||
listBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
separatorColor = self.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
self.componentShadowLayer.shadowOpacity = 0.32
|
||||
self.cloudShadowLayer0.shadowOpacity = 0.32
|
||||
self.cloudShadowLayer1.shadowOpacity = 0.32
|
||||
} else {
|
||||
listBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
separatorColor = self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
self.componentShadowLayer.shadowOpacity = 0.12
|
||||
self.cloudShadowLayer0.shadowOpacity = 0.12
|
||||
self.cloudShadowLayer1.shadowOpacity = 0.12
|
||||
}
|
||||
|
||||
self.cloudLayer0.backgroundColor = listBackgroundColor.cgColor
|
||||
self.cloudLayer1.backgroundColor = listBackgroundColor.cgColor
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
@ -307,11 +327,11 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
strings: self.presentationData.strings,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
emojiContent: emojiContent,
|
||||
backgroundColor: self.presentationData.theme.list.plainBackgroundColor,
|
||||
separatorColor: self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
backgroundColor: listBackgroundColor,
|
||||
separatorColor: separatorColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width - sideInset * 2.0, height: min(300.0, layout.size.height))
|
||||
containerSize: CGSize(width: layout.size.width - sideInset * 2.0, height: min(308.0, layout.size.height))
|
||||
)
|
||||
if let componentView = self.componentHost.view {
|
||||
var animateIn = false
|
||||
@ -334,7 +354,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
sourceOrigin = CGPoint(x: layout.size.width / 2.0, y: floor(layout.size.height / 2.0 - componentSize.height))
|
||||
}
|
||||
|
||||
let componentFrame = CGRect(origin: CGPoint(x: sideInset, y: sourceOrigin.y + 8.0), size: componentSize)
|
||||
let componentFrame = CGRect(origin: CGPoint(x: sideInset, y: sourceOrigin.y + 5.0), size: componentSize)
|
||||
|
||||
if self.componentShadowLayer.bounds.size != componentFrame.size {
|
||||
let componentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: componentFrame.size), cornerRadius: 24.0).cgPath
|
||||
@ -373,7 +393,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
self?.allowsGroupOpacity = false
|
||||
})
|
||||
|
||||
let contentDuration: Double = 0.25
|
||||
let contentDuration: Double = 0.5
|
||||
let contentDelay: Double = 0.14
|
||||
let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
|
||||
|
||||
|
@ -234,8 +234,8 @@ public final class EntityKeyboardAnimationData: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private final class PassthroughLayer: CALayer {
|
||||
var mirrorLayer: CALayer?
|
||||
public final class PassthroughLayer: CALayer {
|
||||
public var mirrorLayer: CALayer?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
@ -249,7 +249,7 @@ private final class PassthroughLayer: CALayer {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
override public var position: CGPoint {
|
||||
get {
|
||||
return super.position
|
||||
} set(value) {
|
||||
@ -260,7 +260,7 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
override var bounds: CGRect {
|
||||
override public var bounds: CGRect {
|
||||
get {
|
||||
return super.bounds
|
||||
} set(value) {
|
||||
@ -271,7 +271,7 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
override var opacity: Float {
|
||||
override public var opacity: Float {
|
||||
get {
|
||||
return super.opacity
|
||||
} set(value) {
|
||||
@ -282,7 +282,7 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
override var sublayerTransform: CATransform3D {
|
||||
override public var sublayerTransform: CATransform3D {
|
||||
get {
|
||||
return super.sublayerTransform
|
||||
} set(value) {
|
||||
@ -293,7 +293,7 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
override var transform: CATransform3D {
|
||||
override public var transform: CATransform3D {
|
||||
get {
|
||||
return super.transform
|
||||
} set(value) {
|
||||
@ -304,7 +304,7 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
override func add(_ animation: CAAnimation, forKey key: String?) {
|
||||
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.add(animation, forKey: key)
|
||||
}
|
||||
@ -312,7 +312,7 @@ private final class PassthroughLayer: CALayer {
|
||||
super.add(animation, forKey: key)
|
||||
}
|
||||
|
||||
override func removeAllAnimations() {
|
||||
override public func removeAllAnimations() {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAllAnimations()
|
||||
}
|
||||
@ -320,7 +320,7 @@ private final class PassthroughLayer: CALayer {
|
||||
super.removeAllAnimations()
|
||||
}
|
||||
|
||||
override func removeAnimation(forKey: String) {
|
||||
override public func removeAnimation(forKey: String) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAnimation(forKey: forKey)
|
||||
}
|
||||
@ -329,6 +329,26 @@ private final class PassthroughLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
open class PassthroughView: UIView {
|
||||
override public static var layerClass: AnyClass {
|
||||
return PassthroughLayer.self
|
||||
}
|
||||
|
||||
public let passthroughView: UIView
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.passthroughView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
(self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private class PassthroughShapeLayer: CAShapeLayer {
|
||||
var mirrorLayer: CAShapeLayer?
|
||||
|
||||
@ -1465,6 +1485,35 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public struct CustomLayout: Equatable {
|
||||
public var itemsPerRow: Int
|
||||
public var itemSize: CGFloat
|
||||
public var sideInset: CGFloat
|
||||
public var itemSpacing: CGFloat
|
||||
|
||||
public init(
|
||||
itemsPerRow: Int,
|
||||
itemSize: CGFloat,
|
||||
sideInset: CGFloat,
|
||||
itemSpacing: CGFloat
|
||||
) {
|
||||
self.itemsPerRow = itemsPerRow
|
||||
self.itemSize = itemSize
|
||||
self.sideInset = sideInset
|
||||
self.itemSpacing = itemSpacing
|
||||
}
|
||||
}
|
||||
|
||||
public final class ExternalBackground {
|
||||
public let effectContainerView: UIView?
|
||||
|
||||
public init(
|
||||
effectContainerView: UIView?
|
||||
) {
|
||||
self.effectContainerView = effectContainerView
|
||||
}
|
||||
}
|
||||
|
||||
public final class InputInteractionHolder {
|
||||
public var inputInteraction: InputInteraction?
|
||||
|
||||
@ -1486,6 +1535,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void)?
|
||||
public let chatPeerId: PeerId?
|
||||
public let peekBehavior: EmojiContentPeekBehavior?
|
||||
public let customLayout: CustomLayout?
|
||||
public let externalBackground: ExternalBackground?
|
||||
|
||||
public init(
|
||||
performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer) -> Void,
|
||||
@ -1500,7 +1551,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
navigationController: @escaping () -> NavigationController?,
|
||||
sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void)?,
|
||||
chatPeerId: PeerId?,
|
||||
peekBehavior: EmojiContentPeekBehavior?
|
||||
peekBehavior: EmojiContentPeekBehavior?,
|
||||
customLayout: CustomLayout?,
|
||||
externalBackground: ExternalBackground?
|
||||
) {
|
||||
self.performItemAction = performItemAction
|
||||
self.deleteBackwards = deleteBackwards
|
||||
@ -1515,6 +1568,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.sendSticker = sendSticker
|
||||
self.chatPeerId = chatPeerId
|
||||
self.peekBehavior = peekBehavior
|
||||
self.customLayout = customLayout
|
||||
self.externalBackground = externalBackground
|
||||
}
|
||||
}
|
||||
|
||||
@ -1812,7 +1867,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var premiumButtonInset: CGFloat
|
||||
var premiumButtonHeight: CGFloat
|
||||
|
||||
init(layoutType: ItemLayoutType, width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], expandedGroupIds: Set<AnyHashable>, curveNearBounds: Bool) {
|
||||
init(layoutType: ItemLayoutType, width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], expandedGroupIds: Set<AnyHashable>, curveNearBounds: Bool, customLayout: CustomLayout?) {
|
||||
self.layoutType = layoutType
|
||||
self.width = width
|
||||
|
||||
@ -1823,6 +1878,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
let minItemsPerRow: Int
|
||||
let minSpacing: CGFloat
|
||||
let itemInsets: UIEdgeInsets
|
||||
switch layoutType {
|
||||
case .compact:
|
||||
minItemsPerRow = 8
|
||||
@ -1831,10 +1887,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.verticalSpacing = 9.0
|
||||
|
||||
if width >= 420.0 {
|
||||
self.itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 5.0, bottom: containerInsets.bottom, right: containerInsets.right + 5.0)
|
||||
itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 5.0, bottom: containerInsets.bottom, right: containerInsets.right + 5.0)
|
||||
minSpacing = 2.0
|
||||
} else {
|
||||
self.itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 7.0, bottom: containerInsets.bottom, right: containerInsets.right + 7.0)
|
||||
itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 7.0, bottom: containerInsets.bottom, right: containerInsets.right + 7.0)
|
||||
minSpacing = 9.0
|
||||
}
|
||||
|
||||
@ -1850,22 +1906,28 @@ public final class EmojiPagerContentComponent: Component {
|
||||
minSpacing = 12.0
|
||||
self.itemDefaultHeaderHeight = 24.0
|
||||
self.itemFeaturedHeaderHeight = 60.0
|
||||
self.itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 10.0, bottom: containerInsets.bottom, right: containerInsets.right + 10.0)
|
||||
itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 10.0, bottom: containerInsets.bottom, right: containerInsets.right + 10.0)
|
||||
self.headerInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 16.0, bottom: containerInsets.bottom, right: containerInsets.right + 16.0)
|
||||
}
|
||||
|
||||
self.verticalGroupDefaultSpacing = 18.0
|
||||
self.verticalGroupFeaturedSpacing = 15.0
|
||||
|
||||
let itemHorizontalSpace = width - self.itemInsets.left - self.itemInsets.right
|
||||
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (self.nativeItemSize + minSpacing)))
|
||||
|
||||
let proposedItemSize = floor((itemHorizontalSpace - minSpacing * (CGFloat(self.itemsPerRow) - 1.0)) / CGFloat(self.itemsPerRow))
|
||||
|
||||
self.visibleItemSize = proposedItemSize < self.nativeItemSize ? proposedItemSize : self.nativeItemSize
|
||||
|
||||
self.horizontalSpacing = floorToScreenPixels((itemHorizontalSpace - self.visibleItemSize * CGFloat(self.itemsPerRow)) / CGFloat(self.itemsPerRow - 1))
|
||||
if let customLayout = customLayout {
|
||||
self.itemsPerRow = customLayout.itemsPerRow
|
||||
self.nativeItemSize = customLayout.itemSize
|
||||
self.visibleItemSize = customLayout.itemSize
|
||||
self.verticalSpacing = 9.0
|
||||
self.itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + customLayout.sideInset, bottom: containerInsets.bottom, right: containerInsets.right + customLayout.sideInset)
|
||||
self.horizontalSpacing = customLayout.itemSpacing
|
||||
} else {
|
||||
self.itemInsets = itemInsets
|
||||
let itemHorizontalSpace = width - self.itemInsets.left - self.itemInsets.right
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (self.nativeItemSize + minSpacing)))
|
||||
let proposedItemSize = floor((itemHorizontalSpace - minSpacing * (CGFloat(self.itemsPerRow) - 1.0)) / CGFloat(self.itemsPerRow))
|
||||
self.visibleItemSize = proposedItemSize < self.nativeItemSize ? proposedItemSize : self.nativeItemSize
|
||||
self.horizontalSpacing = floorToScreenPixels((itemHorizontalSpace - self.visibleItemSize * CGFloat(self.itemsPerRow)) / CGFloat(self.itemsPerRow - 1))
|
||||
}
|
||||
|
||||
let actualContentWidth = self.visibleItemSize * CGFloat(self.itemsPerRow) + self.horizontalSpacing * CGFloat(self.itemsPerRow - 1)
|
||||
self.itemInsets.left = floorToScreenPixels((width - actualContentWidth) / 2.0)
|
||||
@ -2215,7 +2277,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
switch icon {
|
||||
case .premiumStar:
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) {
|
||||
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 8.0, height: size.height - 8.0))
|
||||
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
}
|
||||
@ -2443,8 +2505,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private var vibrancyEffectView: UIVisualEffectView?
|
||||
public private(set) var mirrorContentClippingView: UIView?
|
||||
private let mirrorContentScrollView: UIView
|
||||
private var warpView: WarpView?
|
||||
private var mirrorContentWarpView: WarpView?
|
||||
private let scrollView: ContentScrollView
|
||||
private var scrollGradientLayer: SimpleGradientLayer?
|
||||
private let boundsChangeTrackerLayer = SimpleLayer()
|
||||
@ -2527,16 +2591,19 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
func updateIsWarpEnabled(isEnabled: Bool) {
|
||||
if isEnabled {
|
||||
let warpView: WarpView
|
||||
if let current = self.warpView {
|
||||
warpView = current
|
||||
} else {
|
||||
warpView = WarpView(frame: CGRect())
|
||||
if self.warpView == nil {
|
||||
let warpView = WarpView(frame: CGRect())
|
||||
self.warpView = warpView
|
||||
|
||||
self.insertSubview(warpView, aboveSubview: self.scrollView)
|
||||
warpView.contentView.addSubview(self.scrollView)
|
||||
}
|
||||
if self.mirrorContentWarpView == nil {
|
||||
let mirrorContentWarpView = WarpView(frame: CGRect())
|
||||
self.mirrorContentWarpView = mirrorContentWarpView
|
||||
|
||||
mirrorContentWarpView.contentView.addSubview(self.mirrorContentScrollView)
|
||||
}
|
||||
} else {
|
||||
if let warpView = self.warpView {
|
||||
self.warpView = nil
|
||||
@ -2544,6 +2611,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.insertSubview(self.scrollView, aboveSubview: warpView)
|
||||
warpView.removeFromSuperview()
|
||||
}
|
||||
if let mirrorContentWarpView = self.mirrorContentWarpView {
|
||||
self.mirrorContentWarpView = nil
|
||||
|
||||
if let mirrorContentClippingView = self.mirrorContentClippingView {
|
||||
mirrorContentClippingView.addSubview(self.mirrorContentScrollView)
|
||||
} else if let vibrancyEffectView = self.vibrancyEffectView {
|
||||
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
|
||||
}
|
||||
|
||||
mirrorContentWarpView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2560,11 +2638,31 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let scrollLocation = self.convert(fromLocation, to: self.scrollView)
|
||||
for (_, itemLayer) in self.visibleItemLayers {
|
||||
let distanceVector = CGPoint(x: scrollLocation.x - itemLayer.position.x, y: scrollLocation.y - itemLayer.position.y)
|
||||
let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y + distanceVector.y)
|
||||
let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y)
|
||||
|
||||
let distanceNorm = min(1.0, max(0.0, distance / self.bounds.width))
|
||||
let delay = (distanceNorm) * 0.3
|
||||
itemLayer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, delay: 1.05 * delay)
|
||||
let delay = 0.05 + (distanceNorm) * 0.4
|
||||
itemLayer.animateScale(from: 0.01, to: 1.0, duration: 0.18, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
}
|
||||
|
||||
public func animateInReactionSelection(stationaryItemCount: Int) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
var stationaryItemIds = Set<ItemLayer.Key>()
|
||||
if let group = component.itemGroups.first {
|
||||
for item in group.items {
|
||||
stationaryItemIds.insert(ItemLayer.Key(
|
||||
groupId: group.groupId,
|
||||
itemId: item.content.id
|
||||
))
|
||||
}
|
||||
}
|
||||
for (key, itemLayer) in self.visibleItemLayers {
|
||||
if !stationaryItemIds.contains(key) {
|
||||
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3902,7 +4000,30 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
if keyboardChildEnvironment.theme.overallDarkAppearance || component.warpContentsOnEdges {
|
||||
if let externalBackground = component.inputInteractionHolder.inputInteraction?.externalBackground, let effectContainerView = externalBackground.effectContainerView {
|
||||
let mirrorContentClippingView: UIView
|
||||
if let current = self.mirrorContentClippingView {
|
||||
mirrorContentClippingView = current
|
||||
} else {
|
||||
mirrorContentClippingView = UIView()
|
||||
mirrorContentClippingView.clipsToBounds = true
|
||||
self.mirrorContentClippingView = mirrorContentClippingView
|
||||
|
||||
if let mirrorContentWarpView = self.mirrorContentWarpView {
|
||||
mirrorContentClippingView.addSubview(mirrorContentWarpView)
|
||||
} else {
|
||||
mirrorContentClippingView.addSubview(self.mirrorContentScrollView)
|
||||
}
|
||||
}
|
||||
|
||||
let clippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 42.0), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height))
|
||||
transition.setPosition(view: mirrorContentClippingView, position: clippingFrame.center)
|
||||
transition.setBounds(view: mirrorContentClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: 42.0), size: clippingFrame.size))
|
||||
|
||||
if mirrorContentClippingView.superview !== effectContainerView {
|
||||
effectContainerView.addSubview(mirrorContentClippingView)
|
||||
}
|
||||
} else if keyboardChildEnvironment.theme.overallDarkAppearance || component.warpContentsOnEdges {
|
||||
if let vibrancyEffectView = self.vibrancyEffectView {
|
||||
self.vibrancyEffectView = nil
|
||||
vibrancyEffectView.removeFromSuperview()
|
||||
@ -3916,9 +4037,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
|
||||
self.vibrancyEffectView = vibrancyEffectView
|
||||
self.backgroundView.addSubview(vibrancyEffectView)
|
||||
for subview in vibrancyEffectView.subviews {
|
||||
let _ = subview
|
||||
}
|
||||
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
|
||||
}
|
||||
}
|
||||
@ -4120,7 +4238,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top + 9.0, left: pagerEnvironment.containerInsets.left, bottom: 9.0 + pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right),
|
||||
itemGroups: itemGroups,
|
||||
expandedGroupIds: self.expandedGroupIds,
|
||||
curveNearBounds: component.warpContentsOnEdges
|
||||
curveNearBounds: component.warpContentsOnEdges,
|
||||
customLayout: component.inputInteractionHolder.inputInteraction?.customLayout
|
||||
)
|
||||
if let previousItemLayout = self.itemLayout {
|
||||
if previousItemLayout.width != itemLayout.width {
|
||||
@ -4138,11 +4257,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let previousSize = self.scrollView.bounds.size
|
||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: availableSize)
|
||||
|
||||
let warpHeight: CGFloat = 50.0
|
||||
if let warpView = self.warpView {
|
||||
transition.setFrame(view: warpView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize))
|
||||
let warpHeight: CGFloat = 50.0
|
||||
warpView.update(size: CGSize(width: availableSize.width, height: availableSize.height), topInset: pagerEnvironment.containerInsets.top, warpHeight: warpHeight, theme: keyboardChildEnvironment.theme, transition: transition)
|
||||
}
|
||||
if let mirrorContentWarpView = self.mirrorContentWarpView {
|
||||
transition.setFrame(view: mirrorContentWarpView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize))
|
||||
mirrorContentWarpView.update(size: CGSize(width: availableSize.width, height: availableSize.height), topInset: pagerEnvironment.containerInsets.top, warpHeight: warpHeight, theme: keyboardChildEnvironment.theme, transition: transition)
|
||||
}
|
||||
|
||||
if availableSize.height > previousSize.height || transition.animation.isImmediate {
|
||||
self.boundsChangeTrackerLayer.removeAllAnimations()
|
||||
@ -4165,8 +4288,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
self.scrollView.contentSize = itemLayout.contentSize
|
||||
}
|
||||
if self.scrollView.scrollIndicatorInsets != pagerEnvironment.containerInsets {
|
||||
self.scrollView.scrollIndicatorInsets = pagerEnvironment.containerInsets
|
||||
var scrollIndicatorInsets = pagerEnvironment.containerInsets
|
||||
if self.warpView != nil {
|
||||
scrollIndicatorInsets.bottom += 20.0
|
||||
}
|
||||
if self.scrollView.scrollIndicatorInsets != scrollIndicatorInsets {
|
||||
self.scrollView.scrollIndicatorInsets = scrollIndicatorInsets
|
||||
}
|
||||
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
|
||||
|
||||
|
@ -380,7 +380,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
let nativeIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 24.0, height: 24.0)
|
||||
let boundingIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 38.0, height: 38.0) : CGSize(width: 24.0, height: 24.0)
|
||||
let boundingIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 38.0, height: 38.0) : CGSize(width: 22.0, height: 22.0)
|
||||
|
||||
let iconSize = (self.iconView.image?.size ?? nativeIconSize).aspectFitted(boundingIconSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) / 2.0), y: floor((nativeIconSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
|
@ -7,7 +7,7 @@ import GZip
|
||||
import WebPBinding
|
||||
|
||||
public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOnly: Bool, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
||||
writer.queue.async {
|
||||
let work: () -> Void = {
|
||||
let decompressedData = TGGUnzipData(data, 1 * 1024 * 1024) ?? data
|
||||
guard let animation = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
writer.finish()
|
||||
@ -42,10 +42,12 @@ public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOn
|
||||
|
||||
writer.finish()
|
||||
}
|
||||
|
||||
writer.queue.async(work)
|
||||
}
|
||||
|
||||
public func cacheStillSticker(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter) {
|
||||
writer.queue.async {
|
||||
let work: () -> Void = {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = WebP.convert(fromWebP: data) {
|
||||
writer.add(with: { surface in
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(surface.width), height: CGFloat(surface.height)), scale: 1.0, opaque: false, clear: true, bytesPerRow: surface.bytesPerRow)
|
||||
@ -61,4 +63,6 @@ public func cacheStillSticker(path: String, width: Int, height: Int, writer: Ani
|
||||
|
||||
writer.finish()
|
||||
}
|
||||
|
||||
writer.queue.async(work)
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ private func roundUp(_ numToRound: Int, multiple: Int) -> Int {
|
||||
}
|
||||
|
||||
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
||||
writer.queue.async {
|
||||
let work: () -> Void = {
|
||||
guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil, unpremultiplyAlpha: false) else {
|
||||
return
|
||||
}
|
||||
@ -60,4 +60,6 @@ public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: A
|
||||
|
||||
writer.finish()
|
||||
}
|
||||
|
||||
writer.queue.async(work)
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ReactionExpandArrow.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="15" height="9" viewBox="0 0 15 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 2L7.5 7.5L13 2" stroke="white" stroke-width="1.66" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 213 B |
12
submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "editstatus_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
215
submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf
vendored
Normal file
215
submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 9.575195 15.597168 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.934783 1.711872 m
|
||||
2.934783 0.766384 2.277809 -0.000085 1.467391 -0.000085 c
|
||||
0.656973 -0.000085 0.000000 0.766384 0.000000 1.711872 c
|
||||
0.000000 2.657359 0.656973 3.423828 1.467391 3.423828 c
|
||||
2.277809 3.423828 2.934783 2.657359 2.934783 1.711872 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 17.500000 15.597168 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.934783 1.711872 m
|
||||
2.934783 0.766384 2.277809 -0.000085 1.467391 -0.000085 c
|
||||
0.656973 -0.000085 0.000000 0.766384 0.000000 1.711872 c
|
||||
0.000000 2.657359 0.656973 3.423828 1.467391 3.423828 c
|
||||
2.277809 3.423828 2.934783 2.657359 2.934783 1.711872 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 10.625000 7.951172 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.322874 3.498241 m
|
||||
9.509379 3.814631 9.404086 4.222308 9.087696 4.408813 c
|
||||
8.771306 4.595317 8.363629 4.490025 8.177124 4.173635 c
|
||||
9.322874 3.498241 l
|
||||
h
|
||||
0.572875 4.173635 m
|
||||
0.386370 4.490025 -0.021307 4.595317 -0.337697 4.408813 c
|
||||
-0.654087 4.222308 -0.759380 3.814631 -0.572875 3.498240 c
|
||||
0.572875 4.173635 l
|
||||
h
|
||||
8.749999 3.835938 m
|
||||
8.177124 4.173635 8.177269 4.173880 8.177408 4.174116 c
|
||||
8.177447 4.174182 8.177583 4.174410 8.177661 4.174543 c
|
||||
8.177819 4.174811 8.177957 4.175042 8.178073 4.175239 c
|
||||
8.178307 4.175631 8.178456 4.175881 8.178523 4.175992 c
|
||||
8.178656 4.176214 8.178456 4.175879 8.177924 4.175005 c
|
||||
8.176858 4.173256 8.174464 4.169355 8.170738 4.163445 c
|
||||
8.163282 4.151616 8.150529 4.131790 8.132462 4.105102 c
|
||||
8.096283 4.051657 8.039097 3.971134 7.960825 3.872505 c
|
||||
7.803733 3.674552 7.565047 3.407881 7.244683 3.141313 c
|
||||
6.605815 2.609723 5.657428 2.089324 4.375000 2.089324 c
|
||||
4.375000 0.759324 l
|
||||
6.045980 0.759324 7.285093 1.444732 8.095372 2.118949 c
|
||||
8.499582 2.455284 8.800882 2.791516 9.002632 3.045741 c
|
||||
9.103783 3.173200 9.180732 3.281085 9.233832 3.359525 c
|
||||
9.260406 3.398780 9.281077 3.430766 9.295888 3.454263 c
|
||||
9.303296 3.466016 9.309244 3.475657 9.313739 3.483033 c
|
||||
9.315986 3.486722 9.317870 3.489846 9.319392 3.492387 c
|
||||
9.320154 3.493657 9.320825 3.494782 9.321404 3.495758 c
|
||||
9.321695 3.496246 9.321962 3.496697 9.322207 3.497111 c
|
||||
9.322330 3.497318 9.322496 3.497601 9.322557 3.497704 c
|
||||
9.322719 3.497977 9.322874 3.498241 8.749999 3.835938 c
|
||||
h
|
||||
4.375000 2.089324 m
|
||||
3.092572 2.089324 2.144185 2.609723 1.505316 3.141313 c
|
||||
1.184953 3.407881 0.946267 3.674552 0.789174 3.872505 c
|
||||
0.710903 3.971134 0.653717 4.051657 0.617537 4.105102 c
|
||||
0.599471 4.131790 0.586717 4.151616 0.579261 4.163445 c
|
||||
0.575536 4.169355 0.573141 4.173256 0.572075 4.175005 c
|
||||
0.571543 4.175879 0.571343 4.176215 0.571476 4.175992 c
|
||||
0.571543 4.175881 0.571693 4.175631 0.571926 4.175239 c
|
||||
0.572043 4.175042 0.572180 4.174811 0.572338 4.174543 c
|
||||
0.572417 4.174410 0.572552 4.174182 0.572591 4.174116 c
|
||||
0.572730 4.173880 0.572875 4.173635 0.000000 3.835938 c
|
||||
-0.572875 3.498240 -0.572720 3.497977 -0.572558 3.497704 c
|
||||
-0.572497 3.497601 -0.572330 3.497318 -0.572208 3.497111 c
|
||||
-0.571963 3.496697 -0.571695 3.496246 -0.571405 3.495758 c
|
||||
-0.570825 3.494781 -0.570154 3.493657 -0.569393 3.492387 c
|
||||
-0.567871 3.489846 -0.565986 3.486722 -0.563739 3.483033 c
|
||||
-0.559245 3.475656 -0.553296 3.466016 -0.545889 3.454263 c
|
||||
-0.531078 3.430765 -0.510406 3.398780 -0.483833 3.359525 c
|
||||
-0.430732 3.281085 -0.353783 3.173200 -0.252632 3.045741 c
|
||||
-0.050883 2.791516 0.250417 2.455284 0.654628 2.118948 c
|
||||
1.464907 1.444732 2.704020 0.759324 4.375000 0.759324 c
|
||||
4.375000 2.089324 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 16.487122 0.205078 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.483121 10.765187 m
|
||||
10.742820 10.505488 10.742820 10.084434 10.483121 9.824735 c
|
||||
1.983121 1.324735 l
|
||||
1.723422 1.065036 0.302367 0.065036 0.042669 0.324735 c
|
||||
-0.217030 0.584434 0.782970 2.005488 1.042669 2.265187 c
|
||||
9.542668 10.765187 l
|
||||
9.802367 11.024885 10.223422 11.024885 10.483121 10.765187 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 28.000000 10.540039 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-0.470226 1.930187 m
|
||||
-0.729925 1.670488 -0.729925 1.249434 -0.470226 0.989735 c
|
||||
-0.210527 0.730036 0.210527 0.730036 0.470226 0.989735 c
|
||||
-0.470226 1.930187 l
|
||||
h
|
||||
1.470226 1.989735 m
|
||||
1.729925 2.249434 1.729925 2.670488 1.470226 2.930187 c
|
||||
1.210527 3.189886 0.789473 3.189886 0.529774 2.930187 c
|
||||
1.470226 1.989735 l
|
||||
h
|
||||
0.470226 0.989735 m
|
||||
1.470226 1.989735 l
|
||||
0.529774 2.930187 l
|
||||
-0.470226 1.930187 l
|
||||
0.470226 0.989735 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.500854 2.168945 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
22.334999 12.830078 m
|
||||
22.334999 12.462809 22.632730 12.165078 23.000000 12.165078 c
|
||||
23.367270 12.165078 23.665001 12.462809 23.665001 12.830078 c
|
||||
22.334999 12.830078 l
|
||||
h
|
||||
11.500000 0.665077 m
|
||||
11.867270 0.665077 12.165000 0.962809 12.165000 1.330078 c
|
||||
12.165000 1.697348 11.867270 1.995079 11.500000 1.995079 c
|
||||
11.500000 0.665077 l
|
||||
h
|
||||
11.500000 1.995079 m
|
||||
5.515995 1.995079 0.665000 6.846073 0.665000 12.830078 c
|
||||
-0.665000 12.830078 l
|
||||
-0.665000 6.111534 4.781456 0.665077 11.500000 0.665077 c
|
||||
11.500000 1.995079 l
|
||||
h
|
||||
0.665000 12.830078 m
|
||||
0.665000 18.814083 5.515995 23.665077 11.500000 23.665077 c
|
||||
11.500000 24.995079 l
|
||||
4.781456 24.995079 -0.665000 19.548622 -0.665000 12.830078 c
|
||||
0.665000 12.830078 l
|
||||
h
|
||||
11.500000 23.665077 m
|
||||
17.484005 23.665077 22.334999 18.814083 22.334999 12.830078 c
|
||||
23.665001 12.830078 l
|
||||
23.665001 19.548622 18.218544 24.995079 11.500000 24.995079 c
|
||||
11.500000 23.665077 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
5387
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000005477 00000 n
|
||||
0000005500 00000 n
|
||||
0000005673 00000 n
|
||||
0000005747 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
5806
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "addstatus_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
235
submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf
vendored
Normal file
235
submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 9.575195 15.597168 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.934783 1.711872 m
|
||||
2.934783 0.766384 2.277809 -0.000085 1.467391 -0.000085 c
|
||||
0.656973 -0.000085 0.000000 0.766384 0.000000 1.711872 c
|
||||
0.000000 2.657359 0.656973 3.423828 1.467391 3.423828 c
|
||||
2.277809 3.423828 2.934783 2.657359 2.934783 1.711872 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 17.500000 15.597168 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.934783 1.711872 m
|
||||
2.934783 0.766384 2.277809 -0.000085 1.467391 -0.000085 c
|
||||
0.656973 -0.000085 0.000000 0.766384 0.000000 1.711872 c
|
||||
0.000000 2.657359 0.656973 3.423828 1.467391 3.423828 c
|
||||
2.277809 3.423828 2.934783 2.657359 2.934783 1.711872 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 10.625000 7.951172 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.322874 3.498241 m
|
||||
9.509379 3.814631 9.404086 4.222308 9.087696 4.408813 c
|
||||
8.771306 4.595317 8.363629 4.490025 8.177124 4.173635 c
|
||||
9.322874 3.498241 l
|
||||
h
|
||||
0.572875 4.173635 m
|
||||
0.386370 4.490025 -0.021307 4.595317 -0.337697 4.408813 c
|
||||
-0.654087 4.222308 -0.759380 3.814631 -0.572875 3.498240 c
|
||||
0.572875 4.173635 l
|
||||
h
|
||||
8.749999 3.835938 m
|
||||
8.177124 4.173635 8.177269 4.173880 8.177408 4.174116 c
|
||||
8.177447 4.174182 8.177583 4.174410 8.177661 4.174543 c
|
||||
8.177819 4.174811 8.177957 4.175042 8.178073 4.175239 c
|
||||
8.178307 4.175631 8.178456 4.175881 8.178523 4.175992 c
|
||||
8.178656 4.176214 8.178456 4.175879 8.177924 4.175005 c
|
||||
8.176858 4.173256 8.174464 4.169355 8.170738 4.163445 c
|
||||
8.163282 4.151616 8.150529 4.131790 8.132462 4.105102 c
|
||||
8.096283 4.051657 8.039097 3.971134 7.960825 3.872505 c
|
||||
7.803733 3.674552 7.565047 3.407881 7.244683 3.141313 c
|
||||
6.605815 2.609723 5.657428 2.089324 4.375000 2.089324 c
|
||||
4.375000 0.759324 l
|
||||
6.045980 0.759324 7.285093 1.444732 8.095372 2.118949 c
|
||||
8.499582 2.455284 8.800882 2.791516 9.002632 3.045741 c
|
||||
9.103783 3.173200 9.180732 3.281085 9.233832 3.359525 c
|
||||
9.260406 3.398780 9.281077 3.430766 9.295888 3.454263 c
|
||||
9.303296 3.466016 9.309244 3.475657 9.313739 3.483033 c
|
||||
9.315986 3.486722 9.317870 3.489846 9.319392 3.492387 c
|
||||
9.320154 3.493657 9.320825 3.494782 9.321404 3.495758 c
|
||||
9.321695 3.496246 9.321962 3.496697 9.322207 3.497111 c
|
||||
9.322330 3.497318 9.322496 3.497601 9.322557 3.497704 c
|
||||
9.322719 3.497977 9.322874 3.498241 8.749999 3.835938 c
|
||||
h
|
||||
4.375000 2.089324 m
|
||||
3.092572 2.089324 2.144185 2.609723 1.505316 3.141313 c
|
||||
1.184953 3.407881 0.946267 3.674552 0.789174 3.872505 c
|
||||
0.710903 3.971134 0.653717 4.051657 0.617537 4.105102 c
|
||||
0.599471 4.131790 0.586717 4.151616 0.579261 4.163445 c
|
||||
0.575536 4.169355 0.573141 4.173256 0.572075 4.175005 c
|
||||
0.571543 4.175879 0.571343 4.176215 0.571476 4.175992 c
|
||||
0.571543 4.175881 0.571693 4.175631 0.571926 4.175239 c
|
||||
0.572043 4.175042 0.572180 4.174811 0.572338 4.174543 c
|
||||
0.572417 4.174410 0.572552 4.174182 0.572591 4.174116 c
|
||||
0.572730 4.173880 0.572875 4.173635 0.000000 3.835938 c
|
||||
-0.572875 3.498240 -0.572720 3.497977 -0.572558 3.497704 c
|
||||
-0.572497 3.497601 -0.572330 3.497318 -0.572208 3.497111 c
|
||||
-0.571963 3.496697 -0.571695 3.496246 -0.571405 3.495758 c
|
||||
-0.570825 3.494781 -0.570154 3.493657 -0.569393 3.492387 c
|
||||
-0.567871 3.489846 -0.565986 3.486722 -0.563739 3.483033 c
|
||||
-0.559245 3.475656 -0.553296 3.466016 -0.545889 3.454263 c
|
||||
-0.531078 3.430765 -0.510406 3.398780 -0.483833 3.359525 c
|
||||
-0.430732 3.281085 -0.353783 3.173200 -0.252632 3.045741 c
|
||||
-0.050883 2.791516 0.250417 2.455284 0.654628 2.118948 c
|
||||
1.464907 1.444732 2.704020 0.759324 4.375000 0.759324 c
|
||||
4.375000 2.089324 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 19.000000 4.669922 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 1.995078 m
|
||||
-0.367269 1.995078 -0.665000 1.697348 -0.665000 1.330078 c
|
||||
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
|
||||
0.000000 1.995078 l
|
||||
h
|
||||
10.000000 0.665078 m
|
||||
10.367270 0.665078 10.665000 0.962809 10.665000 1.330078 c
|
||||
10.665000 1.697348 10.367270 1.995078 10.000000 1.995078 c
|
||||
10.000000 0.665078 l
|
||||
h
|
||||
0.000000 0.665078 m
|
||||
10.000000 0.665078 l
|
||||
10.000000 1.995078 l
|
||||
0.000000 1.995078 l
|
||||
0.000000 0.665078 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 35.330078 1.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 11.995078 m
|
||||
-0.367269 11.995078 -0.665000 11.697348 -0.665000 11.330078 c
|
||||
-0.665000 10.962809 -0.367269 10.665078 0.000000 10.665078 c
|
||||
0.000000 11.995078 l
|
||||
h
|
||||
10.000000 10.665078 m
|
||||
10.367270 10.665078 10.665000 10.962809 10.665000 11.330078 c
|
||||
10.665000 11.697348 10.367270 11.995078 10.000000 11.995078 c
|
||||
10.000000 10.665078 l
|
||||
h
|
||||
0.000000 10.665078 m
|
||||
10.000000 10.665078 l
|
||||
10.000000 11.995078 l
|
||||
0.000000 11.995078 l
|
||||
0.000000 10.665078 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.500854 2.168945 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
22.078434 10.474806 m
|
||||
21.998976 10.116235 22.225241 9.761142 22.583811 9.681683 c
|
||||
22.942383 9.602223 23.297476 9.828489 23.376934 10.187059 c
|
||||
22.078434 10.474806 l
|
||||
h
|
||||
14.143019 0.953144 m
|
||||
14.501589 1.032602 14.727855 1.387695 14.648396 1.746267 c
|
||||
14.568936 2.104837 14.213843 2.331102 13.855272 2.251644 c
|
||||
14.143019 0.953144 l
|
||||
h
|
||||
11.500000 1.995079 m
|
||||
5.515995 1.995079 0.665000 6.846073 0.665000 12.830078 c
|
||||
-0.665000 12.830078 l
|
||||
-0.665000 6.111534 4.781456 0.665077 11.500000 0.665077 c
|
||||
11.500000 1.995079 l
|
||||
h
|
||||
0.665000 12.830078 m
|
||||
0.665000 18.814083 5.515995 23.665077 11.500000 23.665077 c
|
||||
11.500000 24.995079 l
|
||||
4.781456 24.995079 -0.665000 19.548622 -0.665000 12.830078 c
|
||||
0.665000 12.830078 l
|
||||
h
|
||||
11.500000 23.665077 m
|
||||
17.484005 23.665077 22.334999 18.814083 22.334999 12.830078 c
|
||||
23.665001 12.830078 l
|
||||
23.665001 19.548622 18.218544 24.995079 11.500000 24.995079 c
|
||||
11.500000 23.665077 l
|
||||
h
|
||||
22.334999 12.830078 m
|
||||
22.334999 12.020529 22.246332 11.232471 22.078434 10.474806 c
|
||||
23.376934 10.187059 l
|
||||
23.565632 11.038589 23.665001 11.923120 23.665001 12.830078 c
|
||||
22.334999 12.830078 l
|
||||
h
|
||||
13.855272 2.251644 m
|
||||
13.097607 2.083746 12.309549 1.995079 11.500000 1.995079 c
|
||||
11.500000 0.665077 l
|
||||
12.406958 0.665077 13.291489 0.764446 14.143019 0.953144 c
|
||||
13.855272 2.251644 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
5910
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000006000 00000 n
|
||||
0000006023 00000 n
|
||||
0000006196 00000 n
|
||||
0000006270 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
6329
|
||||
%%EOF
|
@ -1088,6 +1088,30 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if !actions.reactionItems.isEmpty {
|
||||
let reactionItems: [AvailableReactions.Reaction] = actions.reactionItems.compactMap { item -> AvailableReactions.Reaction? in
|
||||
switch item {
|
||||
case let .reaction(reaction):
|
||||
if let largeApplicationAnimation = reaction.largeApplicationAnimation {
|
||||
return AvailableReactions.Reaction(
|
||||
isEnabled: true,
|
||||
isPremium: false,
|
||||
value: reaction.reaction.rawValue,
|
||||
title: "",
|
||||
staticIcon: reaction.listAnimation,
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
selectAnimation: reaction.stillAnimation,
|
||||
activateAnimation: reaction.largeListAnimation,
|
||||
effectAnimation: largeApplicationAnimation,
|
||||
aroundAnimation: reaction.applicationAnimation,
|
||||
centerAnimation: reaction.listAnimation
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
actions.getEmojiContent = {
|
||||
guard let strongSelf = self else {
|
||||
preconditionFailure()
|
||||
@ -1101,7 +1125,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
isStandalone: false,
|
||||
isStatusSelection: false,
|
||||
isReactionSelection: true,
|
||||
reactionItems: availableReactions.reactions,
|
||||
reactionItems: reactionItems,
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: strongSelf.chatLocation.peerId
|
||||
@ -1265,37 +1289,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
strongSelf.currentContextController = controller
|
||||
|
||||
controller.reactionSelected = { [weak controller] value, isLarge in
|
||||
controller.premiumReactionsSelected = { [weak controller] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if case .premium = value {
|
||||
controller?.dismissWithoutContent()
|
||||
controller?.dismissWithoutContent()
|
||||
|
||||
let context = strongSelf.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .reactions)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
let context = strongSelf.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .reactions)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
}
|
||||
|
||||
controller.reactionSelected = { [weak controller] reaction, isLarge in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let message = messages.first, let reaction = value.reaction else {
|
||||
guard let message = messages.first else {
|
||||
return
|
||||
}
|
||||
|
||||
var updatedReaction: MessageReaction.Reaction? = reaction.rawValue
|
||||
var updatedReaction: UpdateMessageReaction? = reaction
|
||||
var isFirst = true
|
||||
for attribute in topMessage.attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
for existingReaction in attribute.reactions {
|
||||
if existingReaction.value == reaction.rawValue {
|
||||
if existingReaction.value == reaction.reaction {
|
||||
if existingReaction.isSelected {
|
||||
updatedReaction = nil
|
||||
}
|
||||
@ -1303,7 +1330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||
if let current = attribute.value, current == reaction.rawValue {
|
||||
if let current = attribute.value, current == reaction.reaction {
|
||||
updatedReaction = nil
|
||||
}
|
||||
}
|
||||
@ -1313,11 +1340,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
|
||||
if item.message.id == message.id {
|
||||
if let updatedReaction = updatedReaction {
|
||||
itemNode.awaitingAppliedReaction = (updatedReaction, { [weak itemNode] in
|
||||
itemNode.awaitingAppliedReaction = (updatedReaction.reaction, { [weak itemNode] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction.reaction) {
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
|
||||
|
||||
var hideTargetButton: UIView?
|
||||
@ -1325,7 +1352,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
hideTargetButton = targetView.superview
|
||||
}
|
||||
|
||||
controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
||||
controller.dismissWithReaction(value: updatedReaction.reaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1434,12 +1461,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
var updatedReaction: MessageReaction.Reaction?
|
||||
var updatedReaction: UpdateMessageReaction?
|
||||
switch reaction {
|
||||
case .default:
|
||||
updatedReaction = item.associatedData.defaultReaction
|
||||
switch item.associatedData.defaultReaction {
|
||||
case .none:
|
||||
updatedReaction = nil
|
||||
case let .builtin(value):
|
||||
updatedReaction = .builtin(value)
|
||||
case let .custom(fileId):
|
||||
updatedReaction = .custom(fileId: fileId, file: nil)
|
||||
}
|
||||
case let .reaction(value):
|
||||
updatedReaction = value
|
||||
switch value {
|
||||
case let .builtin(value):
|
||||
updatedReaction = .builtin(value)
|
||||
case let .custom(fileId):
|
||||
updatedReaction = .custom(fileId: fileId, file: nil)
|
||||
}
|
||||
}
|
||||
|
||||
var removedReaction: MessageReaction.Reaction?
|
||||
@ -1453,7 +1492,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if listReaction.isSelected {
|
||||
updatedReaction = nil
|
||||
removedReaction = listReaction.value
|
||||
} else if listReaction.value == updatedReaction {
|
||||
} else if listReaction.value == updatedReaction?.reaction {
|
||||
messageAlreadyHasThisReaction = true
|
||||
}
|
||||
case let .reaction(value):
|
||||
@ -1490,7 +1529,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
switch allowedReactions {
|
||||
case let .set(set):
|
||||
if !messageAlreadyHasThisReaction && !set.contains(updatedReaction) {
|
||||
if !messageAlreadyHasThisReaction && !set.contains(updatedReaction.reaction) {
|
||||
itemNode.openMessageContextMenu()
|
||||
return
|
||||
}
|
||||
@ -1503,11 +1542,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
strongSelf.selectPollOptionFeedback?.tap()
|
||||
|
||||
itemNode.awaitingAppliedReaction = (updatedReaction, { [weak itemNode] in
|
||||
itemNode.awaitingAppliedReaction = (updatedReaction.reaction, { [weak itemNode] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
||||
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction.reaction) {
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
@ -1516,7 +1555,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
continue
|
||||
}
|
||||
|
||||
if reaction.value == updatedReaction {
|
||||
if reaction.value == updatedReaction.reaction {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
@ -110,8 +110,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
|
||||
var orderedItemListCollectionIds: [Int32] = []
|
||||
|
||||
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.LocalRecentEmoji)
|
||||
|
||||
if isStatusSelection {
|
||||
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
|
||||
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
|
||||
}
|
||||
|
||||
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.LocalRecentEmoji], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||
ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true),
|
||||
context.account.viewTracker.featuredEmojiPacks()
|
||||
)
|
||||
@ -131,9 +140,15 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var itemGroupIndexById: [AnyHashable: Int] = [:]
|
||||
|
||||
var recentEmoji: OrderedItemListView?
|
||||
var featuredStatusEmoji: OrderedItemListView?
|
||||
var recentStatusEmoji: OrderedItemListView?
|
||||
for orderedView in view.orderedItemListsViews {
|
||||
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
|
||||
recentEmoji = orderedView
|
||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji {
|
||||
featuredStatusEmoji = orderedView
|
||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji {
|
||||
recentStatusEmoji = orderedView
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +167,62 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
|
||||
var existingIds = Set<MediaId>()
|
||||
if let recentStatusEmoji = recentStatusEmoji {
|
||||
for item in recentStatusEmoji.items {
|
||||
guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let file = item.media
|
||||
if existingIds.contains(file.fileId) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(file.fileId)
|
||||
|
||||
let resultItem: EmojiPagerContentComponent.Item
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: file)
|
||||
resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: file,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let featuredStatusEmoji = featuredStatusEmoji {
|
||||
for item in featuredStatusEmoji.items {
|
||||
guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let file = item.media
|
||||
if existingIds.contains(file.fileId) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(file.fileId)
|
||||
|
||||
let resultItem: EmojiPagerContentComponent.Item
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: file)
|
||||
resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: file,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if isReactionSelection {
|
||||
for reactionItem in reactionItems {
|
||||
let animationFile = reactionItem.selectAnimation
|
||||
@ -173,7 +244,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let recentEmoji = recentEmoji, !isReactionSelection {
|
||||
if let recentEmoji = recentEmoji, !isReactionSelection, !isStatusSelection {
|
||||
for item in recentEmoji.items {
|
||||
guard let item = item.contents.get(RecentEmojiItem.self) else {
|
||||
continue
|
||||
@ -1411,7 +1482,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer)
|
||||
},
|
||||
chatPeerId: chatPeerId,
|
||||
peekBehavior: nil
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
externalBackground: nil
|
||||
)
|
||||
|
||||
var stickerPeekBehavior: EmojiContentPeekBehaviorImpl?
|
||||
@ -1609,7 +1682,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer)
|
||||
},
|
||||
chatPeerId: chatPeerId,
|
||||
peekBehavior: stickerPeekBehavior
|
||||
peekBehavior: stickerPeekBehavior,
|
||||
customLayout: nil,
|
||||
externalBackground: nil
|
||||
)
|
||||
|
||||
self.inputDataDisposable = (combineLatest(queue: .mainQueue(),
|
||||
@ -2333,7 +2408,9 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
externalBackground: nil
|
||||
)
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
@ -54,6 +54,9 @@ private final class StatusReactionNode: ASDisplayNode {
|
||||
private var value: MessageReaction.Reaction?
|
||||
private var isSelected: Bool?
|
||||
|
||||
private var resolvedFile: TelegramMediaFile?
|
||||
private var fileDisposable: Disposable?
|
||||
|
||||
override init() {
|
||||
self.iconView = ReactionIconView()
|
||||
|
||||
@ -64,9 +67,10 @@ private final class StatusReactionNode: ASDisplayNode {
|
||||
|
||||
deinit {
|
||||
self.iconImageDisposable.dispose()
|
||||
self.fileDisposable?.dispose()
|
||||
}
|
||||
|
||||
func update(context: AccountContext, type: ChatMessageDateAndStatusType, value: MessageReaction.Reaction, file: TelegramMediaFile?, isSelected: Bool, count: Int, theme: PresentationTheme, wallpaper: TelegramWallpaper, animated: Bool) {
|
||||
func update(context: AccountContext, type: ChatMessageDateAndStatusType, value: MessageReaction.Reaction, file: TelegramMediaFile?, fileId: Int64?, isSelected: Bool, count: Int, theme: PresentationTheme, wallpaper: TelegramWallpaper, animated: Bool) {
|
||||
if self.value != value {
|
||||
self.value = value
|
||||
|
||||
@ -74,7 +78,7 @@ private final class StatusReactionNode: ASDisplayNode {
|
||||
let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0))
|
||||
let imageSize: CGSize
|
||||
if let file = file {
|
||||
self.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0))
|
||||
self.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0), queue: sharedReactionStaticImage)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -87,8 +91,44 @@ private final class StatusReactionNode: ASDisplayNode {
|
||||
}
|
||||
}))
|
||||
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
|
||||
|
||||
self.fileDisposable?.dispose()
|
||||
self.fileDisposable = nil
|
||||
} else if let fileId = fileId {
|
||||
self.fileDisposable?.dispose()
|
||||
self.fileDisposable = nil
|
||||
|
||||
imageSize = boundingImageSize
|
||||
|
||||
if let resolvedFile = self.resolvedFile, resolvedFile.fileId.id == fileId {
|
||||
} else {
|
||||
self.fileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self, let file = result[fileId] else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.resolvedFile = file
|
||||
|
||||
strongSelf.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0), queue: sharedReactionStaticImage)
|
||||
|> deliverOnMainQueue).start(next: { data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
||||
if let image = UIImage(data: dataValue) {
|
||||
strongSelf.iconView.imageView.image = image
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
imageSize = defaultImageSize
|
||||
|
||||
self.fileDisposable?.dispose()
|
||||
self.fileDisposable = nil
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((boundingImageSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||
@ -1037,17 +1077,23 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
validReactions.insert(reaction.value)
|
||||
|
||||
var centerAnimation: TelegramMediaFile?
|
||||
var reactionFileId: Int64?
|
||||
|
||||
if let availableReactions = arguments.availableReactions {
|
||||
for availableReaction in availableReactions.reactions {
|
||||
if availableReaction.value == reaction.value {
|
||||
centerAnimation = availableReaction.centerAnimation
|
||||
break
|
||||
switch reaction.value {
|
||||
case .builtin:
|
||||
if let availableReactions = arguments.availableReactions {
|
||||
for availableReaction in availableReactions.reactions {
|
||||
if availableReaction.value == reaction.value {
|
||||
centerAnimation = availableReaction.centerAnimation
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
reactionFileId = fileId
|
||||
}
|
||||
|
||||
node.update(context: arguments.context, type: arguments.type, value: reaction.value, file: centerAnimation, isSelected: reaction.isSelected, count: Int(reaction.count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
||||
node.update(context: arguments.context, type: arguments.type, value: reaction.value, file: centerAnimation, fileId: reactionFileId, isSelected: reaction.isSelected, count: Int(reaction.count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
||||
if node.supernode == nil {
|
||||
strongSelf.addSubnode(node)
|
||||
if animation.isAnimated {
|
||||
|
@ -2447,7 +2447,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
containerSize: CGSize(width: 34.0, height: 34.0)
|
||||
)
|
||||
let expandedIconSize = self.titleExpandedCredibilityIconView.update(
|
||||
transition: Transition(transition),
|
||||
@ -2470,7 +2470,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
containerSize: CGSize(width: 34.0, height: 34.0)
|
||||
)
|
||||
|
||||
self.credibilityIconSize = iconSize
|
||||
@ -2594,8 +2594,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
titleString = NSAttributedString(string: title, font: Font.medium(29.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
||||
smallTitleString = NSAttributedString(string: title, font: Font.semibold(28.0), textColor: .white)
|
||||
titleString = NSAttributedString(string: title, font: Font.regular(30.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
||||
smallTitleString = NSAttributedString(string: title, font: Font.regular(30.0), textColor: .white)
|
||||
if self.isSettings, let user = peer as? TelegramUser {
|
||||
var subtitle = formatPhoneNumber(user.phone ?? "")
|
||||
|
||||
@ -2708,7 +2708,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
var titleHorizontalOffset: CGFloat = 0.0
|
||||
if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize {
|
||||
titleHorizontalOffset = -(credibilityIconSize.width + 4.0) / 2.0
|
||||
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0, y: floor((titleSize.height - credibilityIconSize.height) / 2.0) + 1.0), size: credibilityIconSize))
|
||||
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize))
|
||||
transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize))
|
||||
}
|
||||
|
||||
@ -2727,7 +2727,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: minTitleFrame.maxY + 2.0), size: subtitleSize)
|
||||
usernameFrame = CGRect(origin: CGPoint(x: width - usernameSize.width - 16.0, y: minTitleFrame.midY - usernameSize.height / 2.0), size: usernameSize)
|
||||
} else {
|
||||
titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 7.0 + (subtitleSize.height.isZero ? 11.0 : 0.0) + 11.0), size: titleSize)
|
||||
titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 9.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize)
|
||||
|
||||
let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width
|
||||
if usernameSize.width == 0.0 {
|
||||
@ -2924,7 +2924,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.avatarListNode.avatarContainerNode.canAttachVideo = false
|
||||
}
|
||||
|
||||
let panelWithAvatarHeight: CGFloat = 40.0 + avatarSize
|
||||
let panelWithAvatarHeight: CGFloat = 35.0 + avatarSize
|
||||
|
||||
let rawHeight: CGFloat
|
||||
let height: CGFloat
|
||||
|
@ -610,24 +610,29 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
||||
let setPhotoTitle: String
|
||||
let displaySetPhoto: Bool
|
||||
if let peer = data.peer, !peer.profileImageRepresentations.isEmpty {
|
||||
setPhotoTitle = presentationData.strings.Settings_SetNewProfilePhotoOrVideo
|
||||
displaySetPhoto = isExpanded
|
||||
setPhotoTitle = presentationData.strings.Settings_ChangeProfilePhoto
|
||||
displaySetPhoto = true
|
||||
} else {
|
||||
setPhotoTitle = presentationData.strings.Settings_SetProfilePhotoOrVideo
|
||||
displaySetPhoto = true
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let setStatusTitle: String = "Set Emoji Status"
|
||||
var setStatusTitle: String = ""
|
||||
let displaySetStatus: Bool
|
||||
var hasEmojiStatus = false
|
||||
if let peer = data.peer as? TelegramUser, peer.isPremium {
|
||||
if peer.emojiStatus != nil {
|
||||
hasEmojiStatus = true
|
||||
}
|
||||
displaySetStatus = true
|
||||
setStatusTitle = "Set Emoji Status"
|
||||
} else {
|
||||
displaySetStatus = false
|
||||
}
|
||||
|
||||
if displaySetStatus {
|
||||
items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setStatusTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
|
||||
items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setStatusTitle, icon: UIImage(bundleImageName: hasEmojiStatus ? "Settings/EditEmojiStatus" : "Settings/SetEmojiStatus"), action: {
|
||||
interaction.openSettings(.emojiStatus)
|
||||
}))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "8.9",
|
||||
"app": "8.9.1",
|
||||
"bazel": "5.1.0",
|
||||
"xcode": "13.4.1"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user