[WIP] Reactions

This commit is contained in:
Ali 2022-08-16 22:19:22 +03:00
parent fb0824ed8b
commit f1e4e2dc7b
35 changed files with 1713 additions and 489 deletions

View File

@ -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";

View File

@ -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() {
}

View File

@ -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
}

View File

@ -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",

View File

@ -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

View File

@ -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])?

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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)
}*/
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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))
})
}

View File

@ -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 {

View File

@ -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
}
}
}

View File

@ -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),

View File

@ -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 {

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ReactionExpandArrow.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "editstatus_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "addstatus_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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)
}))
}

View File

@ -1,5 +1,5 @@
{
"app": "8.9",
"app": "8.9.1",
"bazel": "5.1.0",
"xcode": "13.4.1"
}