Add sticker packs reordering

This commit is contained in:
Ilya Laktyushin 2021-08-11 01:28:16 +03:00
parent 1e0e0de7c8
commit 856638a82f
10 changed files with 555 additions and 86 deletions

View File

@ -869,6 +869,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
self.timer.swap(nil)?.invalidate() self.timer.swap(nil)?.invalidate()
} }
private weak var nodeToCopyFrameFrom: AnimatedStickerNode?
override public func didLoad() { override public func didLoad() {
super.didLoad() super.didLoad()
@ -879,9 +880,23 @@ public final class AnimatedStickerNode: ASDisplayNode {
//self.renderer = MetalAnimationRenderer() //self.renderer = MetalAnimationRenderer()
#endif #endif
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
if let contents = self.nodeToCopyFrameFrom?.renderer?.contents {
self.renderer?.contents = contents
}
self.nodeToCopyFrameFrom = nil
self.addSubnode(self.renderer!) self.addSubnode(self.renderer!)
} }
public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) {
if let renderer = self.renderer {
if let contents = otherNode?.renderer?.contents {
renderer.contents = contents
}
} else {
self.nodeToCopyFrameFrom = otherNode
}
}
public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
if width < 2 || height < 2 { if width < 2 || height < 2 {
return return

View File

@ -300,6 +300,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
private var currentGeneralScrollDirection: GeneralScrollDirection? private var currentGeneralScrollDirection: GeneralScrollDirection?
public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in } public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in }
public private(set) var isReordering = false
public final var willBeginReorder: (CGPoint) -> Void = { _ in }
public final var reorderBegan: () -> Void = { }
public final var reorderItem: (Int, Int, Any?) -> Signal<Bool, NoError> = { _, _, _ in return .single(false) } public final var reorderItem: (Int, Int, Any?) -> Signal<Bool, NoError> = { _, _, _ in return .single(false) }
public final var reorderCompleted: (Any?) -> Void = { _ in } public final var reorderCompleted: (Any?) -> Void = { _ in }
@ -322,8 +325,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
private var reorderNode: ListViewReorderingItemNode? private var reorderNode: ListViewReorderingItemNode?
private var reorderFeedback: HapticFeedback? private var reorderFeedback: HapticFeedback?
private var reorderFeedbackDisposable: MetaDisposable? private var reorderFeedbackDisposable: MetaDisposable?
private var isReorderingItems: Bool = false private var reorderInProgress: Bool = false
private var reorderingItemsCompleted: (() -> Void)? private var reorderingItemsCompleted: (() -> Void)?
public var reorderedItemHasShadow = true
private let waitingForNodesDisposable = MetaDisposable() private let waitingForNodesDisposable = MetaDisposable()
@ -394,15 +398,19 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let itemNodeFrame = itemNode.frame let itemNodeFrame = itemNode.frame
let itemNodeBounds = itemNode.bounds let itemNodeBounds = itemNode.bounds
if itemNode.isReorderable(at: point.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY)) { if itemNode.isReorderable(at: point.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY)) {
strongSelf.beginReordering(itemNode: itemNode) let requiresLongPress = !strongSelf.reorderedItemHasShadow
return true return (true, requiresLongPress, itemNode)
} }
break break
} }
} }
} }
} }
return false return (false, false, nil)
}, willBegin: { [weak self] point in
self?.willBeginReorder(point)
}, began: { [weak self] itemNode in
self?.beginReordering(itemNode: itemNode)
}, ended: { [weak self] in }, ended: { [weak self] in
self?.endReordering() self?.endReordering()
}, moved: { [weak self] offset in }, moved: { [weak self] offset in
@ -469,10 +477,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
private func beginReordering(itemNode: ListViewItemNode) { private func beginReordering(itemNode: ListViewItemNode) {
self.isReordering = true
self.reorderBegan()
if let reorderNode = self.reorderNode { if let reorderNode = self.reorderNode {
reorderNode.removeFromSupernode() reorderNode.removeFromSupernode()
} }
let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin, hasShadow: self.reorderedItemHasShadow)
self.reorderNode = reorderNode self.reorderNode = reorderNode
if let verticalScrollIndicator = self.verticalScrollIndicator { if let verticalScrollIndicator = self.verticalScrollIndicator {
self.insertSubnode(reorderNode, belowSubnode: verticalScrollIndicator) self.insertSubnode(reorderNode, belowSubnode: verticalScrollIndicator)
@ -509,8 +520,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
strongSelf.reorderCompleted(strongSelf.opaqueTransactionState) strongSelf.reorderCompleted(strongSelf.opaqueTransactionState)
strongSelf.isReordering = false
} }
if self.isReorderingItems { if self.reorderInProgress {
self.reorderingItemsCompleted = f self.reorderingItemsCompleted = f
} else { } else {
f() f()
@ -587,14 +599,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if self.reorderFeedbackDisposable == nil { if self.reorderFeedbackDisposable == nil {
self.reorderFeedbackDisposable = MetaDisposable() self.reorderFeedbackDisposable = MetaDisposable()
} }
self.isReorderingItems = true self.reorderInProgress = true
self.reorderFeedbackDisposable?.set((self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState) self.reorderFeedbackDisposable?.set((self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState)
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.isReorderingItems = false strongSelf.reorderInProgress = false
if let reorderingItemsCompleted = strongSelf.reorderingItemsCompleted { if let reorderingItemsCompleted = strongSelf.reorderingItemsCompleted {
strongSelf.reorderingItemsCompleted = nil strongSelf.reorderingItemsCompleted = nil
reorderingItemsCompleted() reorderingItemsCompleted()

View File

@ -1,28 +1,101 @@
import Foundation import Foundation
import UIKit import UIKit
import SwiftSignalKit
final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { public final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
private let shouldBegin: (CGPoint) -> Bool private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?)
private let willBegin: (CGPoint) -> Void
private let began: (ListViewItemNode) -> Void
private let ended: () -> Void private let ended: () -> Void
private let moved: (CGFloat) -> Void private let moved: (CGFloat) -> Void
private var initialLocation: CGPoint? private var initialLocation: CGPoint?
private var longTapTimer: SwiftSignalKit.Timer?
private var longPressTimer: SwiftSignalKit.Timer?
init(shouldBegin: @escaping (CGPoint) -> Bool, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { private var itemNode: ListViewItemNode?
public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (ListViewItemNode) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) {
self.shouldBegin = shouldBegin self.shouldBegin = shouldBegin
self.willBegin = willBegin
self.began = began
self.ended = ended self.ended = ended
self.moved = moved self.moved = moved
super.init(target: nil, action: nil) super.init(target: nil, action: nil)
} }
override func reset() { deinit {
self.longTapTimer?.invalidate()
self.longPressTimer?.invalidate()
}
private func startLongTapTimer() {
self.longTapTimer?.invalidate()
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in
self?.longTapTimerFired()
}, queue: Queue.mainQueue())
self.longTapTimer = longTapTimer
longTapTimer.start()
}
private func stopLongTapTimer() {
self.itemNode = nil
self.longTapTimer?.invalidate()
self.longTapTimer = nil
}
private func startLongPressTimer() {
self.longPressTimer?.invalidate()
let longPressTimer = SwiftSignalKit.Timer(timeout: 0.8, repeat: false, completion: { [weak self] in
self?.longPressTimerFired()
}, queue: Queue.mainQueue())
self.longPressTimer = longPressTimer
longPressTimer.start()
}
private func stopLongPressTimer() {
self.itemNode = nil
self.longPressTimer?.invalidate()
self.longPressTimer = nil
}
override public func reset() {
super.reset() super.reset()
self.itemNode = nil
self.stopLongTapTimer()
self.stopLongPressTimer()
self.initialLocation = nil self.initialLocation = nil
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { private func longTapTimerFired() {
guard let location = self.initialLocation else {
return
}
self.longTapTimer?.invalidate()
self.longTapTimer = nil
self.willBegin(location)
}
private func longPressTimerFired() {
guard let _ = self.initialLocation else {
return
}
self.state = .began
self.longPressTimer?.invalidate()
self.longPressTimer = nil
self.longTapTimer?.invalidate()
self.longTapTimer = nil
if let itemNode = self.itemNode {
self.began(itemNode)
}
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event) super.touchesBegan(touches, with: event)
if self.numberOfTouches > 1 { if self.numberOfTouches > 1 {
@ -32,40 +105,76 @@ final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
} }
if self.state == .possible { if self.state == .possible {
if let location = touches.first?.location(in: self.view), self.shouldBegin(location) { if let location = touches.first?.location(in: self.view) {
self.initialLocation = location let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location)
self.state = .began if allowed {
self.itemNode = itemNode
self.initialLocation = location
if requiresLongPress {
self.startLongTapTimer()
self.startLongPressTimer()
} else {
self.state = .began
}
} else {
self.state = .failed
}
} else { } else {
self.state = .failed self.state = .failed
} }
} }
} }
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) { override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event) super.touchesEnded(touches, with: event)
self.initialLocation = nil
self.stopLongTapTimer()
if self.longPressTimer != nil {
self.stopLongPressTimer()
self.state = .failed
}
if self.state == .began || self.state == .changed { if self.state == .began || self.state == .changed {
self.ended() self.ended()
self.state = .failed self.state = .failed
} }
} }
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) { override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event) super.touchesCancelled(touches, with: event)
self.initialLocation = nil
self.stopLongTapTimer()
if self.longPressTimer != nil {
self.stopLongPressTimer()
self.state = .failed
}
if self.state == .began || self.state == .changed { if self.state == .began || self.state == .changed {
self.ended() self.ended()
self.state = .failed self.state = .failed
} }
} }
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) { override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event) super.touchesMoved(touches, with: event)
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
self.state = .changed self.state = .changed
let offset = location.y - initialLocation.y let offset = location.y - initialLocation.y
self.moved(offset) self.moved(offset)
} else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil {
let touchLocation = touch.location(in: self.view)
let dX = touchLocation.x - initialTapLocation.x
let dY = touchLocation.y - initialTapLocation.y
if dX * dX + dY * dY > 3.0 * 3.0 {
self.stopLongTapTimer()
self.stopLongPressTimer()
self.initialLocation = nil
self.state = .failed
}
} }
} }
} }

View File

@ -25,14 +25,16 @@ private final class CopyView: UIView {
let topShadow: UIImageView let topShadow: UIImageView
let bottomShadow: UIImageView let bottomShadow: UIImageView
override init(frame: CGRect) { init(frame: CGRect, hasShadow: Bool) {
self.topShadow = UIImageView() self.topShadow = UIImageView()
self.bottomShadow = UIImageView() self.bottomShadow = UIImageView()
super.init(frame: frame) super.init(frame: frame)
self.topShadow.image = generateShadowImage(mirror: true) if hasShadow {
self.bottomShadow.image = generateShadowImage(mirror: false) self.topShadow.image = generateShadowImage(mirror: true)
self.bottomShadow.image = generateShadowImage(mirror: false)
}
self.addSubview(self.topShadow) self.addSubview(self.topShadow)
self.addSubview(self.bottomShadow) self.addSubview(self.bottomShadow)
@ -51,9 +53,9 @@ final class ListViewReorderingItemNode: ASDisplayNode {
private let copyView: CopyView private let copyView: CopyView
private let initialLocation: CGPoint private let initialLocation: CGPoint
init(itemNode: ListViewItemNode, initialLocation: CGPoint) { init(itemNode: ListViewItemNode, initialLocation: CGPoint, hasShadow: Bool) {
self.itemNode = itemNode self.itemNode = itemNode
self.copyView = CopyView(frame: CGRect()) self.copyView = CopyView(frame: CGRect(), hasShadow: hasShadow)
let snapshotView = itemNode.snapshotForReordering() let snapshotView = itemNode.snapshotForReordering()
self.initialLocation = initialLocation self.initialLocation = initialLocation
@ -68,8 +70,6 @@ final class ListViewReorderingItemNode: ASDisplayNode {
self.copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: itemNode.bounds.size) self.copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: itemNode.bounds.size)
self.copyView.topShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: -30.0), size: CGSize(width: copyView.bounds.size.width, height: 45.0)) self.copyView.topShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: -30.0), size: CGSize(width: copyView.bounds.size.width, height: 45.0))
self.copyView.bottomShadow.image = generateShadowImage(mirror: false)
self.copyView.bottomShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: self.copyView.bounds.size.height - 15.0), size: CGSize(width: self.copyView.bounds.size.width, height: 45.0)) self.copyView.bottomShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: self.copyView.bounds.size.height - 15.0), size: CGSize(width: self.copyView.bounds.size.width, height: 45.0))
self.copyView.topShadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) self.copyView.topShadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)

View File

@ -6,6 +6,8 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
import TelegramPresentationData import TelegramPresentationData
import AnimatedStickerNode
import TelegramAnimatedStickerNode
enum ChatMediaInputMetaSectionItemType: Equatable { enum ChatMediaInputMetaSectionItemType: Equatable {
case savedStickers case savedStickers
@ -13,10 +15,11 @@ enum ChatMediaInputMetaSectionItemType: Equatable {
case stickersMode case stickersMode
case savedGifs case savedGifs
case trendingGifs case trendingGifs
case gifEmoji(String) case gifEmoji(String, TelegramMediaFile?)
} }
final class ChatMediaInputMetaSectionItem: ListViewItem { final class ChatMediaInputMetaSectionItem: ListViewItem {
let account: Account
let inputNodeInteraction: ChatMediaInputNodeInteraction let inputNodeInteraction: ChatMediaInputNodeInteraction
let type: ChatMediaInputMetaSectionItemType let type: ChatMediaInputMetaSectionItemType
let theme: PresentationTheme let theme: PresentationTheme
@ -27,7 +30,8 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
return true return true
} }
init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) {
self.account = account
self.inputNodeInteraction = inputNodeInteraction self.inputNodeInteraction = inputNodeInteraction
self.type = type self.type = type
self.selectedItem = selected self.selectedItem = selected
@ -41,7 +45,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
Queue.mainQueue().async { Queue.mainQueue().async {
node.inputNodeInteraction = self.inputNodeInteraction node.inputNodeInteraction = self.inputNodeInteraction
node.setItem(item: self) node.setItem(item: self)
node.updateTheme(theme: self.theme, expanded: self.expanded) node.updateTheme(account: self.account, theme: self.theme, expanded: self.expanded)
node.updateIsHighlighted() node.updateIsHighlighted()
node.updateAppearanceTransition(transition: .immediate) node.updateAppearanceTransition(transition: .immediate)
@ -61,7 +65,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
Queue.mainQueue().async { Queue.mainQueue().async {
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in
(node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self) (node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self)
(node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme, expanded: self.expanded) (node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(account: self.account, theme: self.theme, expanded: self.expanded)
}) })
} }
} }
@ -86,6 +90,8 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
private let highlightNode: ASImageNode private let highlightNode: ASImageNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private var animatedStickerNode: AnimatedStickerNode?
private var currentExpanded = false private var currentExpanded = false
var item: ChatMediaInputMetaSectionItem? var item: ChatMediaInputMetaSectionItem?
@ -94,6 +100,23 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
var theme: PresentationTheme? var theme: PresentationTheme?
override var visibility: ListViewItemNodeVisibility {
didSet {
self.visibilityStatus = self.visibility != .none
}
}
private var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
self.animatedStickerNode?.visibility = self.visibilityStatus && loopAnimatedStickers
}
}
}
private let stickerFetchedDisposable = MetaDisposable()
init() { init() {
self.containerNode = ASDisplayNode() self.containerNode = ASDisplayNode()
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
@ -130,6 +153,10 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
self.scalingNode.addSubnode(self.textNodeContainer) self.scalingNode.addSubnode(self.textNodeContainer)
} }
deinit {
self.stickerFetchedDisposable.dispose()
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
} }
@ -146,7 +173,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
} }
} }
func updateTheme(theme: PresentationTheme, expanded: Bool) { func updateTheme(account: Account, theme: PresentationTheme, expanded: Bool) {
let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6) let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6)
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize) self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
@ -174,7 +201,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
case .trendingGifs: case .trendingGifs:
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme) self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
title = "Trending" title = "Trending"
case let .gifEmoji(emoji): case let .gifEmoji(emoji, file):
var emoji = emoji var emoji = emoji
switch emoji { switch emoji {
case "😡": case "😡":
@ -200,16 +227,38 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
default: default:
break break
} }
if emoji == "🥳" { // if emoji == "🥳" {
if #available(iOSApplicationExtension 12.1, iOS 12.1, *) { // if #available(iOSApplicationExtension 12.1, iOS 12.1, *) {
} else { // } else {
emoji = "🎉" // emoji = "🎉"
} // }
} // }
self.imageNode.image = nil self.imageNode.image = nil
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(43.0), textColor: .black)
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) if let file = file {
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
let animatedStickerNode: AnimatedStickerNode
if let current = self.animatedStickerNode {
animatedStickerNode = current
} else {
animatedStickerNode = AnimatedStickerNode()
self.animatedStickerNode = animatedStickerNode
// if let placeholderNode = self.placeholderNode {
// self.scalingNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode)
// } else {
self.scalingNode.addSubnode(animatedStickerNode)
// }
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: file.resource), width: 128, height: 128, mode: .cached)
}
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start())
} else {
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(43.0), textColor: .black)
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
}
} }
} }
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor)
@ -224,7 +273,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width, height: expandedBoundingSize.height))
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
@ -236,6 +285,11 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
self.currentExpanded = expanded self.currentExpanded = expanded
if let animatedStickerNode = self.animatedStickerNode {
animatedStickerNode.frame = self.imageNode.frame
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
}
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize)) expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
} }
@ -256,7 +310,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
if case .trending = inputNodeInteraction.highlightedGifMode { if case .trending = inputNodeInteraction.highlightedGifMode {
isHighlighted = true isHighlighted = true
} }
case let .gifEmoji(emoji): case let .gifEmoji(emoji, _):
if case .emojiSearch(emoji) = inputNodeInteraction.highlightedGifMode { if case .emojiSearch(emoji) = inputNodeInteraction.highlightedGifMode {
isHighlighted = true isHighlighted = true
} }

View File

@ -30,11 +30,20 @@ enum CanInstallPeerSpecificPack {
case available(peer: Peer, dismissed: Bool) case available(peer: Peer, dismissed: Bool)
} }
final class ChatMediaInputPanelOpaqueState {
let entries: [ChatMediaInputPanelEntry]
init(entries: [ChatMediaInputPanelEntry]) {
self.entries = entries
}
}
struct ChatMediaInputPanelTransition { struct ChatMediaInputPanelTransition {
let deletions: [ListViewDeleteItem] let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem] let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
let scrollToItem: ListViewScrollToItem? let scrollToItem: ListViewScrollToItem?
let updateOpaqueState: ChatMediaInputPanelOpaqueState?
} }
struct ChatMediaInputGridTransition { struct ChatMediaInputGridTransition {
@ -55,7 +64,7 @@ func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fr
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates, scrollToItem: scrollToItem) return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates, scrollToItem: scrollToItem, updateOpaqueState: ChatMediaInputPanelOpaqueState(entries: toEntries))
} }
func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition {
@ -153,7 +162,7 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated)
} }
func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] { func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, temporaryPackOrder: [ItemCollectionId]? = nil, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] {
var entries: [ChatMediaInputPanelEntry] = [] var entries: [ChatMediaInputPanelEntry] = []
if hasGifs { if hasGifs {
entries.append(.recentGifs(theme, expanded)) entries.append(.recentGifs(theme, expanded))
@ -189,13 +198,36 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
} }
var index = 0 var index = 0
for (_, info, item) in view.collectionInfos {
if let info = info as? StickerPackCollectionInfo, item != nil { var sortedPacks: [(ItemCollectionId, StickerPackCollectionInfo, StickerPackItem?)] = []
entries.append(.stickerPack(index: index, info: info, topItem: item as? StickerPackItem, theme: theme, expanded: expanded)) for (id, info, item) in view.collectionInfos {
index += 1 if let info = info as? StickerPackCollectionInfo, let item = item as? StickerPackItem {
sortedPacks.append((id, info, item))
} }
} }
if let temporaryPackOrder = temporaryPackOrder {
var packDict: [ItemCollectionId: Int] = [:]
for i in 0 ..< sortedPacks.count {
packDict[sortedPacks[i].0] = i
}
var tempSortedPacks: [(ItemCollectionId, StickerPackCollectionInfo, StickerPackItem?)] = []
var processedPacks = Set<ItemCollectionId>()
for id in temporaryPackOrder {
if let index = packDict[id] {
tempSortedPacks.append(sortedPacks[index])
processedPacks.insert(id)
}
}
let restPacks = sortedPacks.filter { !processedPacks.contains($0.0) }
sortedPacks = restPacks + tempSortedPacks
}
for (_, info, topItem) in sortedPacks {
entries.append(.stickerPack(index: index, info: info, topItem: topItem, theme: theme, expanded: expanded))
index += 1
}
if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack { if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack {
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
} }
@ -206,14 +238,14 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere
return entries return entries
} }
func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], expanded: Bool) -> [ChatMediaInputPanelEntry] { func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], animatedEmojiStickers: [String: [StickerPackItem]], expanded: Bool) -> [ChatMediaInputPanelEntry] {
var entries: [ChatMediaInputPanelEntry] = [] var entries: [ChatMediaInputPanelEntry] = []
entries.append(.stickersMode(theme, expanded)) entries.append(.stickersMode(theme, expanded))
entries.append(.savedGifs(theme, expanded)) entries.append(.savedGifs(theme, expanded))
entries.append(.trendingGifs(theme, expanded)) entries.append(.trendingGifs(theme, expanded))
for reaction in reactions { for reaction in reactions {
entries.append(.gifEmotion(entries.count, theme, reaction, expanded)) entries.append(.gifEmotion(entries.count, theme, reaction, animatedEmojiStickers[reaction]?.first?.file, expanded))
} }
return entries return entries
@ -248,7 +280,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
} }
var trendingIsDismissed = false var trendingIsDismissed = false
if let dismissedTrendingStickerPacks = dismissedTrendingStickerPacks, trendingPacks.map({ $0.info.id.id }) == dismissedTrendingStickerPacks { if let dismissedTrendingStickerPacks = dismissedTrendingStickerPacks, Set(trendingPacks.map({ $0.info.id.id })) == Set(dismissedTrendingStickerPacks) {
trendingIsDismissed = true trendingIsDismissed = true
} }
if !trendingIsDismissed { if !trendingIsDismissed {
@ -470,6 +502,7 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
private var panelFocusTimer: SwiftSignalKit.Timer? private var panelFocusTimer: SwiftSignalKit.Timer?
private var lastReorderItemIndex: Int?
var requestDisableStickerAnimations: ((Bool) -> Void)? var requestDisableStickerAnimations: ((Bool) -> Void)?
@ -514,6 +547,7 @@ final class ChatMediaInputNode: ChatInputNode {
self.listView = ListView() self.listView = ListView()
self.listView.useSingleDimensionTouchPoint = true self.listView.useSingleDimensionTouchPoint = true
self.listView.reorderedItemHasShadow = false
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
self.listView.accessibilityPageScrolledString = { row, count in self.listView.accessibilityPageScrolledString = { row, count in
@ -551,6 +585,147 @@ final class ChatMediaInputNode: ChatInputNode {
super.init() super.init()
let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil)
self.listView.willBeginReorder = { [weak self] point in
self?.listView.beganInteractiveDragging(point)
}
self.listView.reorderBegan = { [weak self] in
self?.stopCollapseTimer()
}
self.listView.reorderItem = { [weak self] fromIndex, toIndex, opaqueState in
guard let entries = (opaqueState as? ChatMediaInputPanelOpaqueState)?.entries else {
return .single(false)
}
self?.lastReorderItemIndex = toIndex
let fromEntry = entries[fromIndex]
guard case let .stickerPack(_, fromPackInfo, _, _, _) = fromEntry else {
return .single(false)
}
var referenceId: ItemCollectionId?
var beforeAll = false
var afterAll = false
if toIndex < entries.count {
switch entries[toIndex] {
case let .stickerPack(_, toPackInfo, _, _, _):
referenceId = toPackInfo.id
default:
if entries[toIndex] < fromEntry {
beforeAll = true
} else {
afterAll = true
}
}
} else {
afterAll = true
}
var currentIds: [ItemCollectionId] = []
for entry in entries {
switch entry {
case let .stickerPack(_, info, _, _, _):
currentIds.append(info.id)
default:
break
}
}
var previousIndex: Int?
for i in 0 ..< currentIds.count {
if currentIds[i] == fromPackInfo.id {
previousIndex = i
currentIds.remove(at: i)
break
}
}
var didReorder = false
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< currentIds.count {
if currentIds[i] == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentIds.insert(fromPackInfo.id, at: i + 1)
} else {
didReorder = previousIndex != i
currentIds.insert(fromPackInfo.id, at: i)
}
inserted = true
break
}
}
if !inserted {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
} else if beforeAll {
didReorder = previousIndex != 0
currentIds.insert(fromPackInfo.id, at: 0)
} else if afterAll {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
temporaryPackOrder.set(.single(currentIds))
return .single(didReorder)
}
self.listView.reorderCompleted = { [weak self] opaqueState in
guard let entries = (opaqueState as? ChatMediaInputPanelOpaqueState)?.entries else {
return
}
var currentIds: [ItemCollectionId] = []
for entry in entries {
switch entry {
case let .stickerPack(_, info, _, _, _):
currentIds.append(info.id)
default:
break
}
}
let _ = (context.account.postbox.transaction { transaction -> Void in
let namespace = Namespaces.ItemCollection.CloudStickerPacks
let infos = transaction.getItemCollectionsInfos(namespace: namespace)
var packDict: [ItemCollectionId: Int] = [:]
for i in 0 ..< infos.count {
packDict[infos[i].0] = i
}
var tempSortedPacks: [(ItemCollectionId, ItemCollectionInfo)] = []
var processedPacks = Set<ItemCollectionId>()
for id in currentIds {
if let index = packDict[id] {
tempSortedPacks.append(infos[index])
processedPacks.insert(id)
}
}
let restPacks = infos.filter { !processedPacks.contains($0.0) }
let sortedPacks = restPacks + tempSortedPacks
addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .sync, noDelay: false)
transaction.replaceItemCollectionInfos(namespace: namespace, itemCollectionInfos: sortedPacks)
}
|> deliverOnMainQueue).start(completed: { [weak self] in
temporaryPackOrder.set(.single(nil))
if let strongSelf = self {
if let lastReorderItemIndex = strongSelf.lastReorderItemIndex {
strongSelf.lastReorderItemIndex = nil
if strongSelf.panelIsFocused {
strongSelf.panelFocusScrollToIndex = lastReorderItemIndex
}
}
}
self?.startCollapseTimer(timeout: 1.0)
})
}
self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in
if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) { if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) {
var index: Int32 = 0 var index: Int32 = 0
@ -885,10 +1060,30 @@ final class ChatMediaInputNode: ChatInputNode {
} }
|> distinctUntilChanged |> distinctUntilChanged
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for case let item as StickerPackItem in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = [item]
}
}
}
default:
break
}
return animatedEmojiStickers
}
let previousView = Atomic<ItemCollectionsView?>(value: nil) let previousView = Atomic<ItemCollectionsView?>(value: nil)
let transitionQueue = Queue() let transitionQueue = Queue()
let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelIsFocusedPromise.get(), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager)) let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelIsFocusedPromise.get(), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager), temporaryPackOrder.get(), animatedEmojiStickers)
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks, temporaryPackOrder, animatedEmojiStickers -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
let (view, viewUpdate) = viewAndUpdate let (view, viewUpdate) = viewAndUpdate
let previous = previousView.swap(view) let previous = previousView.swap(view)
var update = viewUpdate var update = viewUpdate
@ -912,8 +1107,8 @@ final class ChatMediaInputNode: ChatInputNode {
installedPacks.insert(info.0) installedPacks.insert(info.0)
} }
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, expanded: panelExpanded) let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, temporaryPackOrder: temporaryPackOrder, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, expanded: panelExpanded)
let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, expanded: panelExpanded) let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, animatedEmojiStickers: animatedEmojiStickers, expanded: panelExpanded)
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, dismissedTrendingStickerPacks: dismissedTrendingStickerPacks, strings: strings, theme: theme) var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, dismissedTrendingStickerPacks: dismissedTrendingStickerPacks, strings: strings, theme: theme)
if view.higher == nil { if view.higher == nil {
@ -1017,7 +1212,8 @@ final class ChatMediaInputNode: ChatInputNode {
self.listView.beganInteractiveDragging = { [weak self] position in self.listView.beganInteractiveDragging = { [weak self] position in
if let strongSelf = self { if let strongSelf = self {
strongSelf.panelFocusTimer?.invalidate() strongSelf.stopCollapseTimer()
var position = position var position = position
var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
if index == nil { if index == nil {
@ -1050,13 +1246,13 @@ final class ChatMediaInputNode: ChatInputNode {
strongSelf.panelFocusScrollToIndex = nil strongSelf.panelFocusScrollToIndex = nil
strongSelf.panelFocusInitialPosition = nil strongSelf.panelFocusInitialPosition = nil
} }
strongSelf.setupCollapseTimer(timeout: decelerated ? 0.5 : 1.5) strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 1.5)
} }
} }
self.gifListView.beganInteractiveDragging = { [weak self] position in self.gifListView.beganInteractiveDragging = { [weak self] position in
if let strongSelf = self { if let strongSelf = self {
strongSelf.panelFocusTimer?.invalidate() strongSelf.stopCollapseTimer()
var position = position var position = position
var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
if index == nil { if index == nil {
@ -1088,7 +1284,7 @@ final class ChatMediaInputNode: ChatInputNode {
strongSelf.panelFocusScrollToIndex = nil strongSelf.panelFocusScrollToIndex = nil
strongSelf.panelFocusInitialPosition = nil strongSelf.panelFocusInitialPosition = nil
} }
strongSelf.setupCollapseTimer(timeout: decelerated ? 0.5 : 1.5) strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 1.5)
} }
} }
} }
@ -1108,7 +1304,7 @@ final class ChatMediaInputNode: ChatInputNode {
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring)) self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring))
} }
private func setupCollapseTimer(timeout: Double) { private func startCollapseTimer(timeout: Double) {
self.panelFocusTimer?.invalidate() self.panelFocusTimer?.invalidate()
let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in
@ -1124,6 +1320,11 @@ final class ChatMediaInputNode: ChatInputNode {
timer.start() timer.start()
} }
private func stopCollapseTimer() {
self.panelFocusTimer?.invalidate()
self.panelFocusTimer = nil
}
private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) { private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
let canSaveGif: Bool let canSaveGif: Bool
if file.file.media.fileId.namespace == Namespaces.Media.CloudFile { if file.file.media.fileId.namespace == Namespaces.Media.CloudFile {
@ -2024,7 +2225,7 @@ final class ChatMediaInputNode: ChatInputNode {
} }
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
if let targetIndex = self.panelFocusScrollToIndex { if let targetIndex = self.panelFocusScrollToIndex, !self.listView.isReordering {
var position: ListViewScrollPosition var position: ListViewScrollPosition
if self.panelIsFocused { if self.panelIsFocused {
if let initialPosition = self.panelFocusInitialPosition { if let initialPosition = self.panelFocusInitialPosition {
@ -2044,7 +2245,7 @@ final class ChatMediaInputNode: ChatInputNode {
scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
} }
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: nil, completion: { [weak self] _ in self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: transition.updateOpaqueState, completion: { [weak self] _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime)
if !strongSelf.didSetReady { if !strongSelf.didSetReady {

View File

@ -42,7 +42,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
case stickersMode(PresentationTheme, Bool) case stickersMode(PresentationTheme, Bool)
case savedGifs(PresentationTheme, Bool) case savedGifs(PresentationTheme, Bool)
case trendingGifs(PresentationTheme, Bool) case trendingGifs(PresentationTheme, Bool)
case gifEmotion(Int, PresentationTheme, String, Bool) case gifEmotion(Int, PresentationTheme, String, TelegramMediaFile?, Bool)
var stableId: ChatMediaInputPanelEntryStableId { var stableId: ChatMediaInputPanelEntryStableId {
switch self { switch self {
@ -66,7 +66,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
return .savedGifs return .savedGifs
case .trendingGifs: case .trendingGifs:
return .trendingGifs return .trendingGifs
case let .gifEmotion(_, _, emoji, _): case let .gifEmotion(_, _, emoji, _, _):
return .gifEmotion(emoji) return .gifEmotion(emoji)
} }
} }
@ -133,8 +133,15 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsExpanded): case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsFile, lhsExpanded):
if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded { if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsFile, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded {
if let lhsFile = lhsFile, let rhsFile = rhsFile {
if !lhsFile.isEqual(to: rhsFile) {
return false
}
} else if (lhsFile != nil) != (rhsFile != nil) {
return false
}
return true return true
} else { } else {
return false return false
@ -230,11 +237,11 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
default: default:
return true return true
} }
case let .gifEmotion(lhsIndex, _, _, _): case let .gifEmotion(lhsIndex, _, _, _, _):
switch rhs { switch rhs {
case .stickersMode, .savedGifs, .trendingGifs: case .stickersMode, .savedGifs, .trendingGifs:
return false return false
case let .gifEmotion(rhsIndex, _, _, _): case let .gifEmotion(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
default: default:
return true return true
@ -256,12 +263,12 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
inputNodeInteraction.navigateToCollectionId(collectionId) inputNodeInteraction.navigateToCollectionId(collectionId)
}) })
case let .savedStickers(theme, expanded): case let .savedStickers(theme, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: {
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0) let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0)
inputNodeInteraction.navigateToCollectionId(collectionId) inputNodeInteraction.navigateToCollectionId(collectionId)
}) })
case let .recentPacks(theme, expanded): case let .recentPacks(theme, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: {
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0)
inputNodeInteraction.navigateToCollectionId(collectionId) inputNodeInteraction.navigateToCollectionId(collectionId)
}) })
@ -284,19 +291,19 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
inputNodeInteraction.navigateToCollectionId(info.id) inputNodeInteraction.navigateToCollectionId(info.id)
}) })
case let .stickersMode(theme, expanded): case let .stickersMode(theme, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: {
inputNodeInteraction.navigateBackToStickers() inputNodeInteraction.navigateBackToStickers()
}) })
case let .savedGifs(theme, expanded): case let .savedGifs(theme, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: {
inputNodeInteraction.setGifMode(.recent) inputNodeInteraction.setGifMode(.recent)
}) })
case let .trendingGifs(theme, expanded): case let .trendingGifs(theme, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: {
inputNodeInteraction.setGifMode(.trending) inputNodeInteraction.setGifMode(.trending)
}) })
case let .gifEmotion(_, theme, emoji, expanded): case let .gifEmotion(_, theme, emoji, file, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, expanded: expanded, selected: { return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji, file), theme: theme, expanded: expanded, selected: {
inputNodeInteraction.setGifMode(.emojiSearch(emoji)) inputNodeInteraction.setGifMode(.emojiSearch(emoji))
}) })
} }

View File

@ -87,6 +87,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
var inputNodeInteraction: ChatMediaInputNodeInteraction? var inputNodeInteraction: ChatMediaInputNodeInteraction?
var currentCollectionId: ItemCollectionId? var currentCollectionId: ItemCollectionId?
private var account: Account?
private var currentThumbnailItem: StickerPackThumbnailItem? private var currentThumbnailItem: StickerPackThumbnailItem?
private var currentExpanded = false private var currentExpanded = false
private var theme: PresentationTheme? private var theme: PresentationTheme?
@ -172,7 +173,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool) { func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool) {
self.currentCollectionId = collectionId self.currentCollectionId = collectionId
self.account = account
var themeUpdated = false var themeUpdated = false
if self.theme !== theme { if self.theme !== theme {
self.theme = theme self.theme = theme
@ -262,7 +263,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 4.0, height: expandedBoundingSize.height))
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
@ -314,4 +315,74 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
override func animateRemoved(_ currentTimestamp: Double, duration: Double) { override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
} }
override func isReorderable(at point: CGPoint) -> Bool {
if self.bounds.contains(point) {
return true
}
return false
}
override func snapshotForReordering() -> UIView? {
if let account = account, let thumbnailItem = self.currentThumbnailItem {
var imageSize = boundingImageSize
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
let containerNode = ASDisplayNode()
let scalingNode = ASDisplayNode()
containerNode.addSubnode(scalingNode)
containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
var snapshotImageNode: TransformImageNode?
var snapshotAnimationNode: AnimatedStickerNode?
switch thumbnailItem {
case let .still(representation):
imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
let imageNode = TransformImageNode()
let imageApply = imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets()))
imageApply()
imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
scalingNode.addSubnode(imageNode)
snapshotImageNode = imageNode
case let .animated(resource, _):
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 128, height: 128, mode: .direct(cachePathPrefix: nil))
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
scalingNode.addSubnode(animatedStickerNode)
animatedStickerNode.cloneCurrentFrame(from: self.animatedStickerNode)
animatedStickerNode.play(fromIndex: self.animatedStickerNode?.currentFrameIndex)
snapshotAnimationNode = animatedStickerNode
}
containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize)
scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize)
if let titleView = self.titleNode.view.snapshotContentTree() {
titleView.frame = self.titleNode.frame
scalingNode.view.addSubview(titleView)
}
let imageFrame = CGRect(origin: CGPoint(x: (expandedBoundingSize.height - imageSize.width) / 2.0, y: (expandedBoundingSize.width - imageSize.height) / 2.0), size: imageSize)
if let imageNode = snapshotImageNode {
imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize)
imageNode.position = imageFrame.center
}
if let animatedStickerNode = snapshotAnimationNode {
animatedStickerNode.frame = imageFrame
animatedStickerNode.updateLayout(size: imageFrame.size)
}
let expanded = self.currentExpanded
let scale = expanded ? 1.0 : boundingImageScale
let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height)
scalingNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
scalingNode.position = CGPoint(x: boundsSize.width / 2.0 + 3.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0) - 3.0)
return containerNode.view
}
return nil
}
} }

View File

@ -1120,7 +1120,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else { } else {
audioRecordingInfoContainerNode = ASDisplayNode() audioRecordingInfoContainerNode = ASDisplayNode()
self.audioRecordingInfoContainerNode = audioRecordingInfoContainerNode self.audioRecordingInfoContainerNode = audioRecordingInfoContainerNode
self.insertSubnode(audioRecordingInfoContainerNode, at: 0) self.clippingNode.insertSubnode(audioRecordingInfoContainerNode, at: 0)
} }
var animateTimeSlideIn = false var animateTimeSlideIn = false
@ -1148,7 +1148,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self?.interfaceInteraction?.finishMediaRecording(.dismiss) self?.interfaceInteraction?.finishMediaRecording(.dismiss)
}) })
self.audioRecordingCancelIndicator = audioRecordingCancelIndicator self.audioRecordingCancelIndicator = audioRecordingCancelIndicator
self.insertSubnode(audioRecordingCancelIndicator, at: 0) self.clippingNode.insertSubnode(audioRecordingCancelIndicator, at: 0)
} }
let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil) let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil)
@ -1257,7 +1257,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode = AnimationNode(animation: "BinRed") audioRecordingDotNode = AnimationNode(animation: "BinRed")
self.audioRecordingDotNode = audioRecordingDotNode self.audioRecordingDotNode = audioRecordingDotNode
self.audioRecordingDotNodeDismissed = false self.audioRecordingDotNodeDismissed = false
self.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton) self.clippingNode.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton)
self.animatingBinNode?.removeFromSupernode() self.animatingBinNode?.removeFromSupernode()
self.animatingBinNode = nil self.animatingBinNode = nil
} }
@ -1412,7 +1412,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self?.interfaceInteraction?.finishMediaRecording(.send) self?.interfaceInteraction?.finishMediaRecording(.send)
return true return true
} }
self.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons) self.clippingNode.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons)
} }
self.actionButtons.isAccessibilityElement = false self.actionButtons.isAccessibilityElement = false
let size: CGFloat = 120.0 let size: CGFloat = 120.0
@ -1475,7 +1475,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
contextPlaceholderNode.displaysAsynchronously = false contextPlaceholderNode.displaysAsynchronously = false
contextPlaceholderNode.isUserInteractionEnabled = false contextPlaceholderNode.isUserInteractionEnabled = false
self.contextPlaceholderNode = contextPlaceholderNode self.contextPlaceholderNode = contextPlaceholderNode
self.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode) self.clippingNode.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode)
} }
let _ = placeholderApply() let _ = placeholderApply()
@ -1495,7 +1495,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else { } else {
slowmodePlaceholderNode = ChatTextInputSlowmodePlaceholderNode(theme: interfaceState.theme) slowmodePlaceholderNode = ChatTextInputSlowmodePlaceholderNode(theme: interfaceState.theme)
self.slowmodePlaceholderNode = slowmodePlaceholderNode self.slowmodePlaceholderNode = slowmodePlaceholderNode
self.insertSubnode(slowmodePlaceholderNode, aboveSubnode: self.textPlaceholderNode) self.clippingNode.insertSubnode(slowmodePlaceholderNode, aboveSubnode: self.textPlaceholderNode)
} }
let placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: 30.0)) let placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: 30.0))
slowmodePlaceholderNode.updateState(slowmodeState) slowmodePlaceholderNode.updateState(slowmodeState)

View File

@ -71,7 +71,7 @@ private final class PrefetchManagerInnerImpl {
} }
} }
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"] let popularEmoji = ["\u{2764}", "👍", "👎", "😳", "😒", "🥳", "😡", "😮", "😂", "😘", "😍", "🙄", "😎"]
for emoji in popularEmoji { for emoji in popularEmoji {
if let sticker = animatedEmojiStickers[emoji] { if let sticker = animatedEmojiStickers[emoji] {
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) { if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {