From f1e4e2dc7b073ea4514617faca2b51e09a48ae94 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 16 Aug 2022 22:19:22 +0300 Subject: [PATCH] [WIP] Reactions --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/DirectAnimatedStickerNode.swift | 118 +++- .../Sources/ReactionButtonListComponent.swift | 2 +- .../Components/ReactionImageComponent/BUILD | 2 + .../Sources/ReactionImageComponent.swift | 95 ++- .../ContextUI/Sources/ContextController.swift | 11 +- ...tControllerExtractedPresentationNode.swift | 47 +- .../ReactionContextBackgroundNode.swift | 45 +- .../Sources/ReactionContextNode.swift | 612 +++++++++++------- .../Sources/ReactionSelectionNode.swift | 32 +- .../QuickReactionSetupController.swift | 2 +- .../Sources/Account/Account.swift | 2 + .../Sources/State/ManagedRecentStickers.swift | 60 ++ .../Sources/State/MessageReactions.swift | 32 +- .../SyncCore/SyncCore_Namespaces.swift | 2 + .../Stickers/TelegramEngineStickers.swift | 80 +-- .../Sources/DefaultDayPresentationTheme.swift | 2 +- .../Sources/AnimationCache.swift | 9 +- .../EmojiStatusSelectionComponent.swift | 60 +- .../Sources/EmojiPagerContentComponent.swift | 209 ++++-- .../EntityKeyboardTopPanelComponent.swift | 2 +- .../Sources/LottieAnimationCache.swift | 8 +- .../Sources/VideoAnimationCache.swift | 4 +- .../Contents.json | 12 + .../ReactionExpandArrow.svg | 3 + .../EditEmojiStatus.imageset/Contents.json | 12 + .../editstatus_30.pdf | 215 ++++++ .../SetEmojiStatus.imageset/Contents.json | 12 + .../SetEmojiStatus.imageset/addstatus_30.pdf | 235 +++++++ .../TelegramUI/Sources/ChatController.swift | 97 ++- .../Sources/ChatEntityKeyboardInputNode.swift | 87 ++- .../ChatMessageDateAndStatusNode.swift | 62 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 14 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 13 +- versions.json | 2 +- 35 files changed, 1713 insertions(+), 489 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/ReactionExpandArrow.svg create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1277b72c59..827bfd95db 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift index 9a36f9148d..b5ca2678e6 100644 --- a/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift @@ -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() { } diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index da45c7ac1f..3c7a01a093 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -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 } diff --git a/submodules/Components/ReactionImageComponent/BUILD b/submodules/Components/ReactionImageComponent/BUILD index 2791aa47b6..7b26c280e8 100644 --- a/submodules/Components/ReactionImageComponent/BUILD +++ b/submodules/Components/ReactionImageComponent/BUILD @@ -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", diff --git a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift index 91fdea753c..eac694d4dd 100644 --- a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift +++ b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift @@ -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 { +public let sharedReactionStaticImage = Queue(name: "SharedReactionStaticImage", qos: .default) + +public func reactionStaticImage(context: AccountContext, animation: TelegramMediaFile, pixelSize: CGSize, queue: Queue) -> Signal { 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 diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 13d21e0ed8..36e95b5822 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -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])? diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 59bdc6c691..a1e94e99fe 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -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, diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index badab8a0fe..b998f72ac3 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -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) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 4b25931b24..ece25cf123 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -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)? - 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? @@ -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)?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void) { + private var emojiContent: EmojiPagerContentComponent? + private var emojiContentDisposable: Disposable? + + public init(context: AccountContext, presentationData: PresentationData, items: [ReactionContextItem], getEmojiContent: (() -> Signal)?, 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() 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 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() diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index cce873e45c..c03979ca5c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -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) - }*/ } } } diff --git a/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift b/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift index d461d01f24..f7ad0e6757 100644 --- a/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift +++ b/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 583b02fbf1..39b4af6fa4 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift index 0eec62a67d..f620d58f30 100644 --- a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift +++ b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift @@ -202,3 +202,63 @@ func managedAllPremiumStickers(postbox: Postbox, network: Network) -> Signal then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } + +func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal { + 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 { + 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 +} diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 61a8a1c638..02439b97e2 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -4,8 +4,36 @@ import SwiftSignalKit import TelegramApi import MtProtoKit -public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: MessageReaction.Reaction?, isLarge: Bool) -> Signal { +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 { 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)) }) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index e8ec368e85..867ed5690d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 2dab75738d..8795eae4b8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -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() - 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() + 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 } } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 6289309b61..6d9dd8741b 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -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), diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift index 4435f007ba..c20bc18d9c 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift @@ -74,7 +74,7 @@ public struct AnimationCacheItemDrawingSurface { public let bytesPerRow: Int public let length: Int - init( + public init( argb: UnsafeMutablePointer, 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 { diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index ea1d650a72..7e5a698e76 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -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() 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)) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 8a527f0d9c..fc7651eb29 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -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, curveNearBounds: Bool) { + init(layoutType: ItemLayoutType, width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], expandedGroupIds: Set, 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() + 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) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index 6a967a582d..d3d25312d9 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift index 7bb2c4e0d6..6859f2459a 100644 --- a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift +++ b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift @@ -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) } diff --git a/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift b/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift index b1543de3b6..d4cbd23f25 100644 --- a/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift +++ b/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift @@ -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) } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/Contents.json new file mode 100644 index 0000000000..21d9c7e516 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ReactionExpandArrow.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/ReactionExpandArrow.svg b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/ReactionExpandArrow.svg new file mode 100644 index 0000000000..283471ca71 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionExpandArrow.imageset/ReactionExpandArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json new file mode 100644 index 0000000000..0d79db115e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "editstatus_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf b/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf new file mode 100644 index 0000000000..7590051620 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/EditEmojiStatus.imageset/editstatus_30.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json new file mode 100644 index 0000000000..dc01a39874 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "addstatus_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf b/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf new file mode 100644 index 0000000000..98ad809de6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/SetEmojiStatus.imageset/addstatus_30.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 3a5fe4f4dc..bd4c9cef98 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 86bb3766a0..fbb18ec66c 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -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 = 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() + 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) diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index d9f84e54ee..dda5bd45f3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index c52396c8f3..81057faefc 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 0305e4e8e8..52dfe58514 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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) })) } diff --git a/versions.json b/versions.json index 6680658187..a3fafdebf4 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "8.9", + "app": "8.9.1", "bazel": "5.1.0", "xcode": "13.4.1" }