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()
}
private weak var nodeToCopyFrameFrom: AnimatedStickerNode?
override public func didLoad() {
super.didLoad()
@ -879,8 +880,22 @@ public final class AnimatedStickerNode: ASDisplayNode {
//self.renderer = MetalAnimationRenderer()
#endif
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!)
}
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) {
if width < 2 || height < 2 {
@ -973,7 +988,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
private var isSetUpForPlayback = false
public func play(firstFrame: Bool = false, fromIndex: Int? = nil) {
switch self.playbackMode {
case .once:

View File

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

View File

@ -1,28 +1,101 @@
import Foundation
import UIKit
import SwiftSignalKit
final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
private let shouldBegin: (CGPoint) -> Bool
public final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
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 moved: (CGFloat) -> Void
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.willBegin = willBegin
self.began = began
self.ended = ended
self.moved = moved
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()
self.itemNode = nil
self.stopLongTapTimer()
self.stopLongPressTimer()
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)
if self.numberOfTouches > 1 {
@ -32,40 +105,76 @@ final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
}
if self.state == .possible {
if let location = touches.first?.location(in: self.view), self.shouldBegin(location) {
self.initialLocation = location
self.state = .began
if let location = touches.first?.location(in: self.view) {
let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location)
if allowed {
self.itemNode = itemNode
self.initialLocation = location
if requiresLongPress {
self.startLongTapTimer()
self.startLongPressTimer()
} else {
self.state = .began
}
} else {
self.state = .failed
}
} else {
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)
self.initialLocation = nil
self.stopLongTapTimer()
if self.longPressTimer != nil {
self.stopLongPressTimer()
self.state = .failed
}
if self.state == .began || self.state == .changed {
self.ended()
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)
self.initialLocation = nil
self.stopLongTapTimer()
if self.longPressTimer != nil {
self.stopLongPressTimer()
self.state = .failed
}
if self.state == .began || self.state == .changed {
self.ended()
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)
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
self.state = .changed
let offset = location.y - initialLocation.y
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 bottomShadow: UIImageView
override init(frame: CGRect) {
init(frame: CGRect, hasShadow: Bool) {
self.topShadow = UIImageView()
self.bottomShadow = UIImageView()
super.init(frame: frame)
self.topShadow.image = generateShadowImage(mirror: true)
self.bottomShadow.image = generateShadowImage(mirror: false)
if hasShadow {
self.topShadow.image = generateShadowImage(mirror: true)
self.bottomShadow.image = generateShadowImage(mirror: false)
}
self.addSubview(self.topShadow)
self.addSubview(self.bottomShadow)
@ -51,9 +53,9 @@ final class ListViewReorderingItemNode: ASDisplayNode {
private let copyView: CopyView
private let initialLocation: CGPoint
init(itemNode: ListViewItemNode, initialLocation: CGPoint) {
init(itemNode: ListViewItemNode, initialLocation: CGPoint, hasShadow: Bool) {
self.itemNode = itemNode
self.copyView = CopyView(frame: CGRect())
self.copyView = CopyView(frame: CGRect(), hasShadow: hasShadow)
let snapshotView = itemNode.snapshotForReordering()
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.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.topShadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)

View File

@ -6,6 +6,8 @@ import TelegramCore
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import AnimatedStickerNode
import TelegramAnimatedStickerNode
enum ChatMediaInputMetaSectionItemType: Equatable {
case savedStickers
@ -13,10 +15,11 @@ enum ChatMediaInputMetaSectionItemType: Equatable {
case stickersMode
case savedGifs
case trendingGifs
case gifEmoji(String)
case gifEmoji(String, TelegramMediaFile?)
}
final class ChatMediaInputMetaSectionItem: ListViewItem {
let account: Account
let inputNodeInteraction: ChatMediaInputNodeInteraction
let type: ChatMediaInputMetaSectionItemType
let theme: PresentationTheme
@ -27,7 +30,8 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
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.type = type
self.selectedItem = selected
@ -41,7 +45,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
Queue.mainQueue().async {
node.inputNodeInteraction = self.inputNodeInteraction
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.updateAppearanceTransition(transition: .immediate)
@ -61,7 +65,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
Queue.mainQueue().async {
completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in
(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 titleNode: ImmediateTextNode
private var animatedStickerNode: AnimatedStickerNode?
private var currentExpanded = false
var item: ChatMediaInputMetaSectionItem?
@ -94,6 +100,23 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
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() {
self.containerNode = ASDisplayNode()
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)
}
deinit {
self.stickerFetchedDisposable.dispose()
}
override func 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)
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:
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
title = "Trending"
case let .gifEmoji(emoji):
case let .gifEmoji(emoji, file):
var emoji = emoji
switch emoji {
case "😡":
@ -200,16 +227,38 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
default:
break
}
if emoji == "🥳" {
if #available(iOSApplicationExtension 12.1, iOS 12.1, *) {
} else {
emoji = "🎉"
}
}
// if emoji == "🥳" {
// if #available(iOSApplicationExtension 12.1, iOS 12.1, *) {
// } else {
// emoji = "🎉"
// }
// }
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))
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)
if let file = file {
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)
@ -224,7 +273,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
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)))
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 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
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))
}
@ -256,7 +310,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
if case .trending = inputNodeInteraction.highlightedGifMode {
isHighlighted = true
}
case let .gifEmoji(emoji):
case let .gifEmoji(emoji, _):
if case .emojiSearch(emoji) = inputNodeInteraction.highlightedGifMode {
isHighlighted = true
}

View File

@ -30,11 +30,20 @@ enum CanInstallPeerSpecificPack {
case available(peer: Peer, dismissed: Bool)
}
final class ChatMediaInputPanelOpaqueState {
let entries: [ChatMediaInputPanelEntry]
init(entries: [ChatMediaInputPanelEntry]) {
self.entries = entries
}
}
struct ChatMediaInputPanelTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
let scrollToItem: ListViewScrollToItem?
let updateOpaqueState: ChatMediaInputPanelOpaqueState?
}
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 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 {
@ -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)
}
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] = []
if hasGifs {
entries.append(.recentGifs(theme, expanded))
@ -189,13 +198,36 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
}
var index = 0
for (_, info, item) in view.collectionInfos {
if let info = info as? StickerPackCollectionInfo, item != nil {
entries.append(.stickerPack(index: index, info: info, topItem: item as? StickerPackItem, theme: theme, expanded: expanded))
index += 1
var sortedPacks: [(ItemCollectionId, StickerPackCollectionInfo, StickerPackItem?)] = []
for (id, info, item) in view.collectionInfos {
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 {
entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded))
}
@ -206,14 +238,14 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere
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] = []
entries.append(.stickersMode(theme, expanded))
entries.append(.savedGifs(theme, expanded))
entries.append(.trendingGifs(theme, expanded))
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
@ -248,7 +280,7 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
}
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
}
if !trendingIsDismissed {
@ -470,6 +502,7 @@ final class ChatMediaInputNode: ChatInputNode {
}
}
private var panelFocusTimer: SwiftSignalKit.Timer?
private var lastReorderItemIndex: Int?
var requestDisableStickerAnimations: ((Bool) -> Void)?
@ -514,6 +547,7 @@ final class ChatMediaInputNode: ChatInputNode {
self.listView = ListView()
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.scroller.panGestureRecognizer.cancelsTouchesInView = false
self.listView.accessibilityPageScrolledString = { row, count in
@ -551,6 +585,147 @@ final class ChatMediaInputNode: ChatInputNode {
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
if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) {
var index: Int32 = 0
@ -885,10 +1060,30 @@ final class ChatMediaInputNode: ChatInputNode {
}
|> 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 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))
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
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, temporaryPackOrder, animatedEmojiStickers -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
let (view, viewUpdate) = viewAndUpdate
let previous = previousView.swap(view)
var update = viewUpdate
@ -912,8 +1107,8 @@ final class ChatMediaInputNode: ChatInputNode {
installedPacks.insert(info.0)
}
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, expanded: panelExpanded)
let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, 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, 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)
if view.higher == nil {
@ -1017,7 +1212,8 @@ final class ChatMediaInputNode: ChatInputNode {
self.listView.beganInteractiveDragging = { [weak self] position in
if let strongSelf = self {
strongSelf.panelFocusTimer?.invalidate()
strongSelf.stopCollapseTimer()
var position = position
var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
if index == nil {
@ -1050,13 +1246,13 @@ final class ChatMediaInputNode: ChatInputNode {
strongSelf.panelFocusScrollToIndex = 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
if let strongSelf = self {
strongSelf.panelFocusTimer?.invalidate()
strongSelf.stopCollapseTimer()
var position = position
var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
if index == nil {
@ -1088,7 +1284,7 @@ final class ChatMediaInputNode: ChatInputNode {
strongSelf.panelFocusScrollToIndex = 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))
}
private func setupCollapseTimer(timeout: Double) {
private func startCollapseTimer(timeout: Double) {
self.panelFocusTimer?.invalidate()
let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in
@ -1124,6 +1320,11 @@ final class ChatMediaInputNode: ChatInputNode {
timer.start()
}
private func stopCollapseTimer() {
self.panelFocusTimer?.invalidate()
self.panelFocusTimer = nil
}
private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
let canSaveGif: Bool
if file.file.media.fileId.namespace == Namespaces.Media.CloudFile {
@ -2024,7 +2225,7 @@ final class ChatMediaInputNode: ChatInputNode {
}
var scrollToItem: ListViewScrollToItem?
if let targetIndex = self.panelFocusScrollToIndex {
if let targetIndex = self.panelFocusScrollToIndex, !self.listView.isReordering {
var position: ListViewScrollPosition
if self.panelIsFocused {
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)
}
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 {
strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime)
if !strongSelf.didSetReady {

View File

@ -42,7 +42,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
case stickersMode(PresentationTheme, Bool)
case savedGifs(PresentationTheme, Bool)
case trendingGifs(PresentationTheme, Bool)
case gifEmotion(Int, PresentationTheme, String, Bool)
case gifEmotion(Int, PresentationTheme, String, TelegramMediaFile?, Bool)
var stableId: ChatMediaInputPanelEntryStableId {
switch self {
@ -66,7 +66,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
return .savedGifs
case .trendingGifs:
return .trendingGifs
case let .gifEmotion(_, _, emoji, _):
case let .gifEmotion(_, _, emoji, _, _):
return .gifEmotion(emoji)
}
}
@ -133,8 +133,15 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
} else {
return false
}
case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsExpanded):
if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded {
case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsFile, lhsExpanded):
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
} else {
return false
@ -230,11 +237,11 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
default:
return true
}
case let .gifEmotion(lhsIndex, _, _, _):
case let .gifEmotion(lhsIndex, _, _, _, _):
switch rhs {
case .stickersMode, .savedGifs, .trendingGifs:
return false
case let .gifEmotion(rhsIndex, _, _, _):
case let .gifEmotion(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex
default:
return true
@ -256,12 +263,12 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
inputNodeInteraction.navigateToCollectionId(collectionId)
})
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)
inputNodeInteraction.navigateToCollectionId(collectionId)
})
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)
inputNodeInteraction.navigateToCollectionId(collectionId)
})
@ -284,19 +291,19 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
inputNodeInteraction.navigateToCollectionId(info.id)
})
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()
})
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)
})
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)
})
case let .gifEmotion(_, theme, emoji, expanded):
return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, expanded: expanded, selected: {
case let .gifEmotion(_, theme, emoji, file, expanded):
return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji, file), theme: theme, expanded: expanded, selected: {
inputNodeInteraction.setGifMode(.emojiSearch(emoji))
})
}

View File

@ -87,6 +87,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
var inputNodeInteraction: ChatMediaInputNodeInteraction?
var currentCollectionId: ItemCollectionId?
private var account: Account?
private var currentThumbnailItem: StickerPackThumbnailItem?
private var currentExpanded = false
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) {
self.currentCollectionId = collectionId
self.account = account
var themeUpdated = false
if self.theme !== theme {
self.theme = theme
@ -262,7 +263,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
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)))
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 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) {
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 {
audioRecordingInfoContainerNode = ASDisplayNode()
self.audioRecordingInfoContainerNode = audioRecordingInfoContainerNode
self.insertSubnode(audioRecordingInfoContainerNode, at: 0)
self.clippingNode.insertSubnode(audioRecordingInfoContainerNode, at: 0)
}
var animateTimeSlideIn = false
@ -1148,7 +1148,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self?.interfaceInteraction?.finishMediaRecording(.dismiss)
})
self.audioRecordingCancelIndicator = audioRecordingCancelIndicator
self.insertSubnode(audioRecordingCancelIndicator, at: 0)
self.clippingNode.insertSubnode(audioRecordingCancelIndicator, at: 0)
}
let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil)
@ -1257,7 +1257,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode = AnimationNode(animation: "BinRed")
self.audioRecordingDotNode = audioRecordingDotNode
self.audioRecordingDotNodeDismissed = false
self.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton)
self.clippingNode.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton)
self.animatingBinNode?.removeFromSupernode()
self.animatingBinNode = nil
}
@ -1412,7 +1412,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self?.interfaceInteraction?.finishMediaRecording(.send)
return true
}
self.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons)
self.clippingNode.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons)
}
self.actionButtons.isAccessibilityElement = false
let size: CGFloat = 120.0
@ -1475,7 +1475,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
contextPlaceholderNode.displaysAsynchronously = false
contextPlaceholderNode.isUserInteractionEnabled = false
self.contextPlaceholderNode = contextPlaceholderNode
self.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode)
self.clippingNode.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode)
}
let _ = placeholderApply()
@ -1495,7 +1495,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else {
slowmodePlaceholderNode = ChatTextInputSlowmodePlaceholderNode(theme: interfaceState.theme)
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))
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 {
if let sticker = animatedEmojiStickers[emoji] {
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {