From fa93c6d791f9d7b27e12e9807a1bd6360f10834f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 21 Feb 2023 22:12:36 +0400 Subject: [PATCH] Cleanup --- .../Sources/ChatControllerNode.swift | 99 +- .../Sources/ChatInterfaceInputNodes.swift | 4 +- .../Sources/ChatMediaInputGifPane.swift | 399 --- .../Sources/ChatMediaInputGridEntries.swift | 234 -- .../ChatMediaInputMetaSectionItemNode.swift | 342 --- .../Sources/ChatMediaInputNode.swift | 2719 ----------------- .../Sources/ChatMediaInputPane.swift | 25 - .../Sources/ChatMediaInputPanelEntries.swift | 337 -- .../ChatMediaInputPeerSpecificItem.swift | 180 -- .../ChatMediaInputRecentGifsItem.swift | 159 - .../Sources/ChatMediaInputSettingsItem.swift | 143 - .../ChatMediaInputStickerGridItem.swift | 17 +- .../ChatMediaInputStickerPackItem.swift | 410 --- .../Sources/ChatMediaInputStickerPane.swift | 202 -- .../Sources/ChatMediaInputTrendingItem.swift | 186 -- ...StickerPanePeerSpecificSetupGridItem.swift | 191 -- .../StickerPaneTrendingListGridItem.swift | 554 ---- .../StickersChatInputContextPanelItem.swift | 239 -- .../StickersChatInputContextPanelNode.swift | 406 --- submodules/TelegramUI/Sources/TelegramUI.h | 18 - 20 files changed, 18 insertions(+), 6846 deletions(-) delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputNode.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputPane.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift delete mode 100644 submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift delete mode 100644 submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift delete mode 100644 submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift delete mode 100644 submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift delete mode 100644 submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift delete mode 100644 submodules/TelegramUI/Sources/TelegramUI.h diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 78185c45a0..6fed82e4f0 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -147,7 +147,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private(set) var textInputPanelNode: ChatTextInputPanelNode? - private var inputMediaNode: ChatMediaInputNode? private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData? private var inputMediaNodeDataPromise = Promise() private var didInitializeInputMediaNodeDataPromise: Bool = false @@ -823,7 +822,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func inFocusUpdated(isInFocus: Bool) { self.isInFocus = isInFocus - self.inputMediaNode?.simulateUpdateLayout(isVisible: isInFocus) if let inputNode = self.inputNode as? ChatEntityKeyboardInputNode { inputNode.simulateUpdateLayout(isVisible: isInFocus) } @@ -1180,7 +1178,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { previewing = false } - let inputNodeForState = inputNodeForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentNode: self.inputNode, interfaceInteraction: self.interfaceInteraction, inputMediaNode: self.inputMediaNode, controllerInteraction: self.controllerInteraction, inputPanelNode: self.inputPanelNode, makeMediaInputNode: { + let inputNodeForState = inputNodeForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentNode: self.inputNode, interfaceInteraction: self.interfaceInteraction, controllerInteraction: self.controllerInteraction, inputPanelNode: self.inputPanelNode, makeMediaInputNode: { return self.makeMediaInputNode() }) @@ -1335,19 +1333,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var immediatelyLayoutInputNodeAndAnimateAppearance = false var inputNodeHeightAndOverflow: (CGFloat, CGFloat)? if let inputNode = inputNodeForState { - if self.inputMediaNode != nil { - if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { - if inputPanelNode.isFocused { - self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) - } - } - } - if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil { - self.inputMediaNode = inputMediaNode - inputMediaNode.requestDisableStickerAnimations = { [weak self] disabled in - self?.controller?.disableStickerAnimations = disabled - } - } if self.inputNode != inputNode { inputNode.topBackgroundExtensionUpdated = { [weak self] transition in self?.updateInputPanelBackgroundExtension(transition: transition) @@ -1377,15 +1362,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputNode.layer.removeAnimation(forKey: "opacity") immediatelyLayoutInputNodeAndAnimateAppearance = true - if self.inputMediaNode != nil { - if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { - self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: inputPanelNode) - } else { - self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) - } - } else { - self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) - } + self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) if let externalTopPanelContainer = inputNode.externalTopPanelContainer { if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { @@ -1452,10 +1429,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.historyNode.isSelectionGestureEnabled = isSelectionEnabled - if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { - let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, layoutMetrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isVisible: false, isExpanded: self.inputPanelContainerNode.stableIsExpanded) - } - transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0))) transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) @@ -2673,33 +2646,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputMediaNodeDataPromise.set(ChatEntityKeyboardInputNode.inputData(context: self.context, interfaceInteraction: interfaceInteraction, controllerInteraction: self.controllerInteraction, chatPeerId: self.chatLocation.peerId, areCustomEmojiEnabled: areCustomEmojiEnabled)) } - if self.inputMediaNode == nil && !"".isEmpty { - let peerId: PeerId? = self.chatPresentationInterfaceState.chatLocation.peerId - let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in - if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { - interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in - if case let .media(_, expanded, focused) = state.inputMode { - if value { - return (.media(mode: .gif, expanded: expanded, focused: focused), nil) - } else { - return (.media(mode: .other, expanded: expanded, focused: focused), nil) - } - } else { - return (state.inputMode, nil) - } - } - } - }) - inputNode.interfaceInteraction = interfaceInteraction - inputNode.requestDisableStickerAnimations = { [weak self] disabled in - self?.controller?.disableStickerAnimations = disabled - } - self.inputMediaNode = inputNode - if let (validLayout, _) = self.validLayout { - let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, layoutMetrics: validLayout.metrics, deviceMetrics: validLayout.deviceMetrics, isVisible: false, isExpanded: self.inputPanelContainerNode.stableIsExpanded) - } - } - self.textInputPanelNode?.loadTextInputNodeIfNeeded() } @@ -2933,12 +2879,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let inputMediaNode = self.inputMediaNode, self.inputNode === inputMediaNode { - let convertedPoint = self.view.convert(point, to: inputMediaNode.view) - if inputMediaNode.point(inside: convertedPoint, with: event) { - return inputMediaNode.hitTest(convertedPoint, with: event) - } - } switch self.chatPresentationInterfaceState.mode { case .standard(previewing: true): if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node, node is ChatMessageSelectionNode || node is GridMessageSelectionNode { @@ -3130,31 +3070,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func openStickers(beginWithEmoji: Bool) { self.openStickersBeginWithEmoji = beginWithEmoji - if let inputMediaNode = self.inputMediaNode { - if self.openStickersDisposable == nil { - self.openStickersDisposable = (inputMediaNode.ready - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] in - self?.openStickersDisposable = nil - self?.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in - return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) - }) + if self.openStickersDisposable == nil { + self.openStickersDisposable = (self.inputMediaNodeDataPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + + strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in + return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) }) - } - } else { - if self.openStickersDisposable == nil { - self.openStickersDisposable = (self.inputMediaNodeDataPromise.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] _ in - guard let strongSelf = self else { - return - } - - strongSelf.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in - return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) - }) - }) - } + }) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 9df65c6b71..44a66da9e6 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -9,7 +9,7 @@ import ChatControllerInteraction import ChatInputNode import ChatEntityKeyboardInputNode -func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentNode: ChatInputNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, inputMediaNode: ChatMediaInputNode?, controllerInteraction: ChatControllerInteraction, inputPanelNode: ChatInputPanelNode?, makeMediaInputNode: () -> ChatInputNode?) -> ChatInputNode? { +func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentNode: ChatInputNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, controllerInteraction: ChatControllerInteraction, inputPanelNode: ChatInputPanelNode?, makeMediaInputNode: () -> ChatInputNode?) -> ChatInputNode? { if let inputPanelNode = inputPanelNode, !(inputPanelNode is ChatTextInputPanelNode) { return nil } @@ -17,8 +17,6 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: case .media: if let currentNode = currentNode as? ChatEntityKeyboardInputNode { return currentNode - } else if let inputMediaNode = inputMediaNode { - return inputMediaNode } else if let inputMediaNode = makeMediaInputNode() { inputMediaNode.interfaceInteraction = interfaceInteraction return inputMediaNode diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift deleted file mode 100644 index 062b1196af..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift +++ /dev/null @@ -1,399 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramPresentationData -import ContextUI -import AccountContext -import ChatPresentationInterfaceState -import ChatControllerInteraction -import MultiplexedVideoNode -import FeaturedStickersScreen -import ChatEntityKeyboardInputNode - -private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) { - let searchBarHeight: CGFloat = 56.0 - - let contentOffset = multiplexedNode.scrollNode.view.contentOffset.y - let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) - - if contentOffset < 60.0 { - if contentOffset < searchBarHeight * 0.6 { - transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: CGRect(origin: CGPoint(), size: multiplexedNode.bounds.size)) - } else { - transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 60.0), size: multiplexedNode.bounds.size)) - } - } -} - -final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { - private let context: AccountContext - private var theme: PresentationTheme - private var strings: PresentationStrings - private let controllerInteraction: ChatControllerInteraction - - private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void - private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void - private let openGifContextMenu: (MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void - - private let searchPlaceholderNode: PaneSearchBarPlaceholderNode - var visibleSearchPlaceholderNode: PaneSearchBarPlaceholderNode? { - guard let scrollNode = multiplexedNode?.scrollNode else { - return nil - } - if scrollNode.bounds.contains(self.searchPlaceholderNode.frame) { - return self.searchPlaceholderNode - } - return nil - } - - private var multiplexedNode: MultiplexedVideoNode? - private let emptyNode: ImmediateTextNode - - private let disposable = MetaDisposable() - let trendingPromise = Promise(nil) - - private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool, DeviceMetrics)? - private var didScrollPreviousOffset: CGFloat? - - private var didScrollPreviousState: ChatMediaInputPaneScrollState? - - private(set) var mode: ChatMediaInputGifMode = .recent - private var isLoadingMore: Bool = false - private var nextOffset: String? - - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void, openGifContextMenu: @escaping (MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void) { - self.context = context - self.theme = theme - self.strings = strings - self.controllerInteraction = controllerInteraction - self.paneDidScroll = paneDidScroll - self.fixPaneScroll = fixPaneScroll - self.openGifContextMenu = openGifContextMenu - - self.searchPlaceholderNode = PaneSearchBarPlaceholderNode() - - self.emptyNode = ImmediateTextNode() - self.emptyNode.isUserInteractionEnabled = false - self.emptyNode.attributedText = NSAttributedString(string: strings.Gif_NoGifsPlaceholder, font: Font.regular(15.0), textColor: theme.chat.inputMediaPanel.stickersSectionTextColor) - self.emptyNode.textAlignment = .center - self.emptyNode.maximumNumberOfLines = 3 - self.emptyNode.isHidden = true - - super.init() - - self.addSubnode(self.emptyNode) - - self.searchPlaceholderNode.activate = { [weak self] in - self?.inputNodeInteraction?.toggleSearch(true, .gif, "") - } - - self.updateThemeAndStrings(theme: theme, strings: strings) - } - - deinit { - self.disposable.dispose() - } - - override func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme - self.strings = strings - - self.emptyNode.attributedText = NSAttributedString(string: strings.Gif_NoGifsPlaceholder, font: Font.regular(15.0), textColor: theme.chat.inputMediaPanel.stickersSectionTextColor) - - self.searchPlaceholderNode.setup(theme: theme, strings: strings, type: .gifs) - - if let layout = self.validLayout { - self.updateLayout(size: layout.0, topInset: layout.1, bottomInset: layout.2, isExpanded: layout.3, isVisible: layout.4, deviceMetrics: layout.5, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { - var changedIsExpanded = false - if let (_, _, _, previousIsExpanded, _, _) = self.validLayout { - if previousIsExpanded != isExpanded { - changedIsExpanded = true - } - } - self.validLayout = (size, topInset, bottomInset, isExpanded, isVisible, deviceMetrics) - - let emptySize = self.emptyNode.updateLayout(size) - transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize)) - - self.updateMultiplexedNodeLayout(changedIsExpanded: changedIsExpanded, transition: transition) - } - - func fileAt(point: CGPoint) -> (MultiplexedVideoNodeFile, CGRect, Bool)? { - if let multiplexedNode = self.multiplexedNode { - return multiplexedNode.fileAt(point: point.offsetBy(dx: -multiplexedNode.frame.minX, dy: -multiplexedNode.frame.minY)) - } else { - return nil - } - } - - func setMode(mode: ChatMediaInputGifMode) { - if self.mode == mode { - if let multiplexedNode = self.multiplexedNode { - multiplexedNode.scrollNode.view.setContentOffset(CGPoint(), animated: true) - } - return - } - self.mode = mode - self.resetMode(synchronous: true, searchOffset: nil) - } - - override var isEmpty: Bool { - if let files = self.multiplexedNode?.files { - return files.trending.isEmpty && files.saved.isEmpty - } else { - return true - } - } - - override func willEnterHierarchy() { - super.willEnterHierarchy() - - self.initializeIfNeeded() - } - - private func updateMultiplexedNodeLayout(changedIsExpanded: Bool, transition: ContainedViewLayoutTransition) { - guard let (size, topInset, bottomInset, _, _, deviceMetrics) = self.validLayout else { - return - } - - if let multiplexedNode = self.multiplexedNode { - let _ = multiplexedNode.scrollNode.layer.bounds - - let displaySearch: Bool - - switch self.mode { - case .recent, .trending: - displaySearch = true - default: - displaySearch = false - } - - multiplexedNode.topInset = topInset + (displaySearch ? 60.0 : 0.0) - multiplexedNode.bottomInset = bottomInset - - if case .tablet = deviceMetrics.type, size.width > 480.0 { - multiplexedNode.idealHeight = 120.0 - } else { - multiplexedNode.idealHeight = 93.0 - } - - let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) - - /*var targetBounds = CGRect(origin: previousBounds.origin, size: nodeFrame.size) - if changedIsExpanded { - let isEmpty = multiplexedNode.files.trending.isEmpty && multiplexedNode.files.saved.isEmpty - //targetBounds.origin.y = isExpanded || isEmpty ? 0.0 : 60.0 - }*/ - - //transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: targetBounds) - transition.updateFrame(node: multiplexedNode, frame: nodeFrame) - - multiplexedNode.updateLayout(theme: self.theme, strings: self.strings, size: nodeFrame.size, transition: transition) - self.searchPlaceholderNode.frame = CGRect(x: 0.0, y: 41.0, width: size.width, height: 56.0) - } - } - - func initializeIfNeeded() { - if self.multiplexedNode == nil { - self.trendingPromise.set(paneGifSearchForQuery(context: self.context, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil) - |> map { items -> ChatMediaInputGifPaneTrendingState? in - if let items = items { - return ChatMediaInputGifPaneTrendingState(files: items.files, nextOffset: items.nextOffset) - } else { - return nil - } - }) - - let multiplexedNode = MultiplexedVideoNode(account: self.context.account, theme: self.theme, strings: self.strings) - self.multiplexedNode = multiplexedNode - if let layout = self.validLayout { - multiplexedNode.frame = CGRect(origin: CGPoint(), size: layout.0) - } - - multiplexedNode.reactionSelected = { [weak self] reaction in - guard let strongSelf = self else { - return - } - strongSelf.inputNodeInteraction?.toggleSearch(true, .gif, reaction) - } - - self.addSubnode(multiplexedNode) - multiplexedNode.scrollNode.addSubnode(self.searchPlaceholderNode) - - multiplexedNode.fileSelected = { [weak self] file, sourceNode, sourceRect in - if let (collection, result) = file.contextResult { - let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode.view, sourceRect, false) - } else { - let _ = self?.controllerInteraction.sendGif(file.file, sourceNode.view, sourceRect, false, false) - } - } - - multiplexedNode.fileContextMenu = { [weak self] fileReference, sourceNode, sourceRect, gesture, isSaved in - self?.openGifContextMenu(fileReference, sourceNode, sourceRect, gesture, isSaved) - } - - multiplexedNode.didScroll = { [weak self] offset, height in - guard let strongSelf = self, let multiplexedNode = strongSelf.multiplexedNode else { - return - } - let absoluteOffset = -offset + 60.0 - var delta: CGFloat = 0.0 - if let didScrollPreviousOffset = strongSelf.didScrollPreviousOffset { - delta = absoluteOffset - didScrollPreviousOffset - } - strongSelf.didScrollPreviousOffset = absoluteOffset - let state = ChatMediaInputPaneScrollState(absoluteOffset: absoluteOffset, relativeChange: delta) - strongSelf.didScrollPreviousState = state - strongSelf.paneDidScroll(strongSelf, state, .immediate) - - if offset >= height - multiplexedNode.bounds.height - 200.0 { - strongSelf.loadMore() - } - } - - multiplexedNode.didEndScrolling = { [weak self] in - guard let strongSelf = self else { - return - } - if let didScrollPreviousState = strongSelf.didScrollPreviousState { - strongSelf.fixPaneScroll(strongSelf, didScrollPreviousState) - } - - if let _ = strongSelf.multiplexedNode { - //fixListScrolling(multiplexedNode) - } - } - - self.updateMultiplexedNodeLayout(changedIsExpanded: false, transition: .immediate) - - self.resetMode(synchronous: false, searchOffset: nil) - } - } - - private func resetMode(synchronous: Bool, searchOffset: String?) { - self.isLoadingMore = true - - let filesSignal: Signal<(MultiplexedVideoNodeFiles, String?), NoError> - switch self.mode { - case .recent: - filesSignal = combineLatest( - self.trendingPromise.get(), - self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs)) - ) - |> map { trending, cloudRecentGifs -> (MultiplexedVideoNodeFiles, String?) in - var saved: [MultiplexedVideoNodeFile] = [] - - saved = cloudRecentGifs.map { item in - let file = item.contents.get(RecentMediaItem.self)!.media - return MultiplexedVideoNodeFile(file: .savedGif(media: file), contextResult: nil) - } - - return (MultiplexedVideoNodeFiles(saved: saved, trending: trending?.files ?? [], isSearch: false, canLoadMore: false, isStale: false), nil) - } - case .trending: - if let searchOffset = searchOffset { - filesSignal = paneGifSearchForQuery(context: self.context, query: "", offset: searchOffset, incompleteResults: true, delayRequest: false, updateActivity: nil) - |> map { result -> (MultiplexedVideoNodeFiles, String?) in - let canLoadMore: Bool - if let result = result { - canLoadMore = !result.isComplete - } else { - canLoadMore = true - } - return (MultiplexedVideoNodeFiles(saved: [], trending: result?.files ?? [], isSearch: true, canLoadMore: canLoadMore, isStale: false), result?.nextOffset) - } - } else { - filesSignal = self.trendingPromise.get() - |> map { trending -> (MultiplexedVideoNodeFiles, String?) in - return (MultiplexedVideoNodeFiles(saved: [], trending: trending?.files ?? [], isSearch: true, canLoadMore: false, isStale: false), trending?.nextOffset) - } - } - case let .emojiSearch(emoji): - filesSignal = paneGifSearchForQuery(context: self.context, query: emoji, offset: searchOffset, incompleteResults: true, staleCachedResults: searchOffset == nil, delayRequest: false, updateActivity: nil) - |> map { result -> (MultiplexedVideoNodeFiles, String?) in - let canLoadMore: Bool - if let result = result { - canLoadMore = !result.isComplete - } else { - canLoadMore = true - } - return (MultiplexedVideoNodeFiles(saved: [], trending: result?.files ?? [], isSearch: true, canLoadMore: canLoadMore, isStale: result?.isStale ?? false), result?.nextOffset) - } - } - - var firstTime = true - - self.disposable.set((filesSignal - |> deliverOnMainQueue).start(next: { [weak self] addedFiles, nextOffset in - if let strongSelf = self { - var resetScrollingToOffset: CGFloat? - if firstTime { - firstTime = false - if searchOffset == nil { - resetScrollingToOffset = 0.0 - } - } - - strongSelf.isLoadingMore = false - - let displaySearch: Bool - - switch strongSelf.mode { - case .recent, .trending: - displaySearch = true - default: - displaySearch = false - } - - strongSelf.searchPlaceholderNode.isHidden = !displaySearch - - if let (_, topInset, _, _, _, _) = strongSelf.validLayout { - strongSelf.multiplexedNode?.topInset = topInset + (displaySearch ? 60.0 : 0.0) - } - - var files = addedFiles - if let _ = searchOffset { - var resultFiles: [MultiplexedVideoNodeFile] = [] - if let currentFiles = strongSelf.multiplexedNode?.files.trending { - resultFiles = currentFiles - } - var existingFileIds = Set(resultFiles.map { $0.file.media.fileId }) - for file in addedFiles.trending { - if existingFileIds.contains(file.file.media.fileId) { - continue - } - existingFileIds.insert(file.file.media.fileId) - resultFiles.append(file) - } - files = MultiplexedVideoNodeFiles(saved: [], trending: resultFiles, isSearch: true, canLoadMore: addedFiles.canLoadMore, isStale: addedFiles.isStale) - } - - strongSelf.nextOffset = nextOffset - strongSelf.multiplexedNode?.setFiles(files: files, synchronous: synchronous, resetScrollingToOffset: resetScrollingToOffset) - } - })) - } - - private func loadMore() { - if self.isLoadingMore { - return - } - guard let nextOffset = self.nextOffset else { - return - } - switch self.mode { - case .trending, .emojiSearch: - self.resetMode(synchronous: false, searchOffset: nextOffset) - default: - break - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift deleted file mode 100644 index 195234e36b..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift +++ /dev/null @@ -1,234 +0,0 @@ -import Postbox -import UIKit -import TelegramCore -import SwiftSignalKit -import Display -import TelegramPresentationData -import MergeLists -import ChatPresentationInterfaceState -import ChatControllerInteraction -import FeaturedStickersScreen - -enum ChatMediaInputGridEntryStableId: Equatable, Hashable { - case search - case trendingList - case peerSpecificSetup - case sticker(ItemCollectionId, ItemCollectionItemIndex.Id) - case trending(ItemCollectionId) -} - -enum ChatMediaInputGridEntryIndex: Equatable, Comparable { - case search - case trendingList - case peerSpecificSetup(dismissed: Bool) - case collectionIndex(ItemCollectionViewEntryIndex) - case trending(ItemCollectionId, Int) - - var stableId: ChatMediaInputGridEntryStableId { - switch self { - case .search: - return .search - case .trendingList: - return .trendingList - case .peerSpecificSetup: - return .peerSpecificSetup - case let .collectionIndex(index): - return .sticker(index.collectionId, index.itemIndex.id) - case let .trending(id, _): - return .trending(id) - } - } - - static func <(lhs: ChatMediaInputGridEntryIndex, rhs: ChatMediaInputGridEntryIndex) -> Bool { - switch lhs { - case .search: - if case .search = rhs { - return false - } else { - return true - } - case .trendingList: - switch rhs { - case .search, .trendingList: - return false - case .peerSpecificSetup, .collectionIndex, .trending: - return true - } - case let .peerSpecificSetup(lhsDismissed): - switch rhs { - case .search, .trendingList, .peerSpecificSetup: - return false - case let .collectionIndex(index): - if lhsDismissed { - return false - } else { - if index.collectionId.id == 0 { - return false - } else { - return true - } - } - case .trending: - return true - } - case let .collectionIndex(lhsIndex): - switch rhs { - case .search, .trendingList: - return false - case let .peerSpecificSetup(dismissed): - if dismissed { - return true - } else { - return false - } - case let .collectionIndex(rhsIndex): - return lhsIndex < rhsIndex - case .trending: - return true - } - case let .trending(_, lhsIndex): - switch rhs { - case .search, .trendingList, .peerSpecificSetup, .collectionIndex: - return false - case let .trending(_, rhsIndex): - return lhsIndex < rhsIndex - } - } - } -} - -enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { - case search(theme: PresentationTheme, strings: PresentationStrings) - case trendingList(theme: PresentationTheme, strings: PresentationStrings, packs: [FeaturedStickerPackItem], isPremium: Bool) - case peerSpecificSetup(theme: PresentationTheme, strings: PresentationStrings, dismissed: Bool) - case sticker(index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, stickerPackInfo: StickerPackCollectionInfo?, canManagePeerSpecificPack: Bool?, maybeManageable: Bool, theme: PresentationTheme, isLocked: Bool) - case trending(TrendingPanePackEntry) - - var index: ChatMediaInputGridEntryIndex { - switch self { - case .search: - return .search - case .trendingList: - return .trendingList - case let .peerSpecificSetup(_, _, dismissed): - return .peerSpecificSetup(dismissed: dismissed) - case let .sticker(index, _, _, _, _, _, _): - return .collectionIndex(index) - case let .trending(entry): - return .trending(entry.info.id, entry.index) - } - } - - var stableId: ChatMediaInputGridEntryStableId { - return self.index.stableId - } - - static func ==(lhs: ChatMediaInputGridEntry, rhs: ChatMediaInputGridEntry) -> Bool { - switch lhs { - case let .search(lhsTheme, lhsStrings): - if case let .search(rhsTheme, rhsStrings) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } - return true - } else { - return false - } - case let .trendingList(lhsTheme, lhsStrings, lhsPacks, lhsIsPremium): - if case let .trendingList(rhsTheme, rhsStrings, rhsPacks, rhsIsPremium) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } - if lhsPacks.count != rhsPacks.count { - return false - } - for i in 0 ..< lhsPacks.count { - if lhsPacks[i].unread != rhsPacks[i].unread { - return false - } - if lhsPacks[i].info != rhsPacks[i].info { - return false - } - } - if lhsIsPremium != rhsIsPremium { - return false - } - return true - } else { - return false - } - case let .peerSpecificSetup(lhsTheme, lhsStrings, lhsDismissed): - if case let .peerSpecificSetup(rhsTheme, rhsStrings, rhsDismissed) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDismissed == rhsDismissed { - return true - } else { - return false - } - case let .sticker(lhsIndex, lhsStickerItem, lhsStickerPackInfo, lhsCanManagePeerSpecificPack, lhsMaybeManageable, lhsTheme, lhsIsLocked): - if case let .sticker(rhsIndex, rhsStickerItem, rhsStickerPackInfo, rhsCanManagePeerSpecificPack, rhsMaybeManageable, rhsTheme, rhsIsLocked) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsStickerItem != rhsStickerItem { - return false - } - if lhsStickerPackInfo != rhsStickerPackInfo { - return false - } - if lhsCanManagePeerSpecificPack != rhsCanManagePeerSpecificPack { - return false - } - if lhsMaybeManageable != rhsMaybeManageable { - return false - } - if lhsTheme !== rhsTheme { - return false - } - if lhsIsLocked != rhsIsLocked { - return false - } - return true - } else { - return false - } - case let .trending(entry): - if case .trending(entry) = rhs { - return true - } else { - return false - } - } - } - - static func <(lhs: ChatMediaInputGridEntry, rhs: ChatMediaInputGridEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(account: Account, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> GridItem { - switch self { - case let .search(theme, strings): - return PaneSearchBarPlaceholderItem(theme: theme, strings: strings, type: .stickers, activate: { - inputNodeInteraction.toggleSearch(true, .sticker, "") - }) - case let .trendingList(theme, strings, packs, isPremium): - return StickerPaneTrendingListGridItem(account: account, theme: theme, strings: strings, trendingPacks: packs, isPremium: isPremium, inputNodeInteraction: inputNodeInteraction, dismiss: { - inputNodeInteraction.dismissTrendingPacks(packs.map { $0.info.id }) - }) - case let .peerSpecificSetup(theme, strings, dismissed): - return StickerPanePeerSpecificSetupGridItem(theme: theme, strings: strings, setup: { - inputNodeInteraction.openPeerSpecificSettings() - }, dismiss: dismissed ? nil : { - inputNodeInteraction.dismissPeerSpecificSettings() - }) - case let .sticker(index, stickerItem, stickerPackInfo, canManagePeerSpecificPack, maybeManageable, theme, isLocked): - return ChatMediaInputStickerGridItem(account: account, collectionId: index.collectionId, stickerPackInfo: stickerPackInfo, index: index, stickerItem: stickerItem, canManagePeerSpecificPack: canManagePeerSpecificPack, interfaceInteraction: interfaceInteraction, inputNodeInteraction: inputNodeInteraction, hasAccessory: maybeManageable, theme: theme, isLocked: isLocked, selected: { }) - case let .trending(entry): - return entry.item(account: account, interaction: trendingInteraction, grid: false) - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift deleted file mode 100644 index 8e60709938..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift +++ /dev/null @@ -1,342 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import ChatPresentationInterfaceState - -enum ChatMediaInputMetaSectionItemType: Equatable { - case savedStickers - case recentStickers - case stickersMode - case savedGifs - case trendingGifs - case premium - case gifEmoji(String, TelegramMediaFile?) -} - -final class ChatMediaInputMetaSectionItem: ListViewItem { - let account: Account - let inputNodeInteraction: ChatMediaInputNodeInteraction - let type: ChatMediaInputMetaSectionItemType - let theme: PresentationTheme - let strings: PresentationStrings - let expanded: Bool - let selectedItem: () -> Void - - var selectable: Bool { - return true - } - - init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool, selected: @escaping () -> Void) { - self.account = account - self.inputNodeInteraction = inputNodeInteraction - self.type = type - self.selectedItem = selected - self.theme = theme - self.strings = strings - self.expanded = expanded - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputMetaSectionItemNode() - Queue.mainQueue().async { - node.inputNodeInteraction = self.inputNodeInteraction - node.setItem(item: self) - node.updateTheme(account: self.account, theme: self.theme, strings: self.strings, expanded: self.expanded) - node.updateIsHighlighted() - node.updateAppearanceTransition(transition: .immediate) - - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - - completion(node, { - return (nil, { _ in - - }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in - (node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self) - (node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(account: self.account, theme: self.theme, strings: self.strings, expanded: self.expanded) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageScale: CGFloat = 0.625 -private let highlightSize = CGSize(width: 56.0, height: 56.0) -private let verticalOffset: CGFloat = 3.0 + UIScreenPixel - -final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let imageNode: ASImageNode - private let textNodeContainer: ASDisplayNode - private let textNode: ImmediateTextNode - private let highlightNode: ASImageNode - private let titleNode: ImmediateTextNode - - private var animatedStickerNode: AnimatedStickerNode? - - private var currentExpanded = false - - var item: ChatMediaInputMetaSectionItem? - var currentCollectionId: ItemCollectionId? - var inputNodeInteraction: ChatMediaInputNodeInteraction? - - 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) - - self.scalingNode = ASDisplayNode() - - self.highlightNode = ASImageNode() - self.highlightNode.isLayerBacked = true - self.highlightNode.isHidden = true - - self.imageNode = ASImageNode() - self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .center - - self.textNodeContainer = ASDisplayNode() - self.textNodeContainer.isUserInteractionEnabled = false - - self.textNode = ImmediateTextNode() - self.textNode.displaysAsynchronously = false - self.textNode.isUserInteractionEnabled = false - - self.textNodeContainer.addSubnode(self.textNode) - self.textNodeContainer.isUserInteractionEnabled = false - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.highlightNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.imageNode) - self.scalingNode.addSubnode(self.textNodeContainer) - } - - deinit { - self.stickerFetchedDisposable.dispose() - } - - override func didLoad() { - super.didLoad() - } - - func setItem(item: ChatMediaInputMetaSectionItem) { - self.item = item - switch item.type { - case .savedStickers: - self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0) - case .recentStickers: - self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) - case .premium: - self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0) - default: - break - } - } - - func updateTheme(account: Account, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool) { - let imageSize = CGSize(width: 44.0, height: 42.0) - 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.textNodeContainer.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + 1.0), size: imageSize) - - if self.theme !== theme { - self.theme = theme - - self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) - var title = "" - if let item = self.item { - switch item.type { - case .savedStickers: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSavedStickersIcon(theme) - title = strings.Stickers_Favorites - case .recentStickers: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme) - title = strings.Stickers_Recent - case .stickersMode: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelStickersModeIcon(theme) - title = strings.Stickers_Stickers - case .savedGifs: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentStickersIcon(theme) - title = strings.Stickers_Gifs - case .trendingGifs: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme) - title = strings.Stickers_Trending - case .premium: - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelPremiumIcon(theme) - title = strings.Stickers_PremiumStickers - case let .gifEmoji(emoji, file): - switch emoji { - case "😡": - title = strings.Gif_Emotion_Angry - case "😮": - title = strings.Gif_Emotion_Surprised - case "😂": - title = strings.Gif_Emotion_Joy - case "😘": - title = strings.Gif_Emotion_Kiss - case "😍": - title = strings.Gif_Emotion_Hearts - case "👍": - title = strings.Gif_Emotion_ThumbsUp - case "👎": - title = strings.Gif_Emotion_ThumbsDown - case "🙄": - title = strings.Gif_Emotion_RollEyes - case "😎": - title = strings.Gif_Emotion_Cool - case "🥳": - title = strings.Gif_Emotion_Party - default: - break - } - self.imageNode.image = nil - - if let file = file { - let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false - let animatedStickerNode: AnimatedStickerNode - if let current = self.animatedStickerNode { - animatedStickerNode = current - } else { - animatedStickerNode = DefaultAnimatedStickerNodeImpl() - self.animatedStickerNode = animatedStickerNode - self.scalingNode.addSubnode(animatedStickerNode) - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: file.resource), width: 128, height: 128, playbackMode: .loop, mode: .cached) - } - animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers - - self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, 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.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 + 10.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) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - 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)) - } - - func updateIsHighlighted() { - guard let inputNodeInteraction = self.inputNodeInteraction else { - return - } - if let currentCollectionId = self.currentCollectionId { - self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId - } else if let item = self.item { - var isHighlighted = false - switch item.type { - case .savedGifs: - if case .recent = inputNodeInteraction.highlightedGifMode { - isHighlighted = true - } - case .trendingGifs: - if case .trending = inputNodeInteraction.highlightedGifMode { - isHighlighted = true - } - case let .gifEmoji(emoji, _): - if case .emojiSearch(emoji) = inputNodeInteraction.highlightedGifMode { - isHighlighted = true - } - default: - break - } - self.highlightNode.isHidden = !isHighlighted - } - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - self.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift deleted file mode 100644 index c4f9f8d95b..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ /dev/null @@ -1,2719 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramPresentationData -import TelegramUIPreferences -import TelegramNotices -import MergeLists -import AccountContext -import StickerPackPreviewUI -import StickerPeekUI -import PeerInfoUI -import SettingsUI -import ContextUI -import GalleryUI -import OverlayStatusController -import PresentationDataUtils -import ChatInterfaceState -import ChatPresentationInterfaceState -import UndoUI -import PremiumUI -import ChatControllerInteraction -import FeaturedStickersScreen -import ChatInputNode -import FeaturedStickersScreen -import MultiplexedVideoNode -import ChatEntityKeyboardInputNode - -struct PeerSpecificPackData { - let peer: Peer - let info: StickerPackCollectionInfo - let items: [ItemCollectionItem] -} - -enum CanInstallPeerSpecificPack { - case none - 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 { - let deletions: [Int] - let insertions: [GridNodeInsertItem] - let updates: [GridNodeUpdateItem] - let updateFirstIndexInSectionOffset: Int? - let stationaryItems: GridNodeStationaryItems - let scrollToItem: GridNodeScrollToItem? - let updateOpaqueState: ChatMediaInputStickerPaneOpaqueState? - let animated: Bool -} - -func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction, scrollToItem: ListViewScrollToItem?) -> ChatMediaInputPanelTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, 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) } - - 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 { - var stationaryItems: GridNodeStationaryItems = .none - var scrollToItem: GridNodeScrollToItem? - var animated = false - switch update { - case .initial: - for i in (0 ..< toEntries.count).reversed() { - switch toEntries[i] { - case .search, .peerSpecificSetup, .trending: - break - case .trendingList, .sticker: - scrollToItem = GridNodeScrollToItem(index: i, position: .top(0.0), transition: .immediate, directionHint: .down, adjustForSection: true, adjustForTopInset: true) - } - } - case .generic: - animated = true - case .scroll: - var fromStableIds = Set() - for entry in fromEntries { - fromStableIds.insert(entry.stableId) - } - var index = 0 - var indices = Set() - for entry in toEntries { - if fromStableIds.contains(entry.stableId) { - indices.insert(index) - } - index += 1 - } - stationaryItems = .indices(indices) - case let .navigate(index, collectionId): - if let index = index.flatMap({ ChatMediaInputGridEntryIndex.collectionIndex($0) }) { - for i in 0 ..< toEntries.count { - if toEntries[i].index >= index { - var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up - if !fromEntries.isEmpty && fromEntries[0].index < toEntries[i].index { - directionHint = .down - } - scrollToItem = GridNodeScrollToItem(index: i, position: .top(0.0), transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) - break - } - } - } else if !toEntries.isEmpty { - if let collectionId = collectionId { - for i in 0 ..< toEntries.count { - var indexMatches = false - switch toEntries[i].index { - case let .collectionIndex(collectionIndex): - if collectionIndex.collectionId == collectionId { - indexMatches = true - } - case .peerSpecificSetup: - if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue { - indexMatches = true - } - default: - break - } - if indexMatches { - var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up - if !fromEntries.isEmpty && fromEntries[0].index < toEntries[i].index { - directionHint = .down - } - scrollToItem = GridNodeScrollToItem(index: i, position: .top(0.0), transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) - break - } - } - } - if scrollToItem == nil { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.45, curve: .spring), directionHint: .up, adjustForSection: true, adjustForTopInset: true) - } - } - } - - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices - let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousIndex: $0.2) } - let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction)) } - - var firstIndexInSectionOffset = 0 - if !toEntries.isEmpty { - switch toEntries[0].index { - case .search, .trendingList, .peerSpecificSetup, .trending: - break - case let .collectionIndex(index): - firstIndexInSectionOffset = Int(index.itemIndex.index) - } - } - - let opaqueState = ChatMediaInputStickerPaneOpaqueState(hasLower: view.lower != nil) - - 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?, temporaryPackOrder: [ItemCollectionId]? = nil, trendingIsDismissed: Bool = false, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme, strings: PresentationStrings, hasPremiumStickers: Bool = false, cloudPremiumStickers: OrderedItemListView? = nil, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false, reorderable: Bool = false) -> [ChatMediaInputPanelEntry] { - var entries: [ChatMediaInputPanelEntry] = [] - if hasGifs { - entries.append(.recentGifs(theme, strings, expanded)) - } - if trendingIsDismissed { - entries.append(.trending(true, theme, strings, expanded)) - } - if let savedStickers = savedStickers, !savedStickers.items.isEmpty { - entries.append(.savedStickers(theme, strings, expanded)) - } - var savedStickerIds = Set() - if let savedStickers = savedStickers, !savedStickers.items.isEmpty { - for i in 0 ..< savedStickers.items.count { - if let item = savedStickers.items[i].contents.get(SavedStickerItem.self) { - savedStickerIds.insert(item.file.fileId.id) - } - } - } - if let recentStickers = recentStickers, !recentStickers.items.isEmpty { - var found = false - for item in recentStickers.items { - if let item = item.contents.get(RecentMediaItem.self), let mediaId = item.media.id { - if !savedStickerIds.contains(mediaId.id) { - found = true - break - } - } - } - if found { - entries.append(.recentPacks(theme, strings, expanded)) - } - } - if let peerSpecificPack = peerSpecificPack { - entries.append(.peerSpecific(theme: theme, peer: peerSpecificPack.peer, expanded: expanded)) - } else if case let .available(peer, false) = canInstallPeerSpecificPack { - entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) - } - - if hasPremiumStickers { - entries.append(.premium(theme, strings, expanded)) - } - - var index = 0 - 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() - 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, reorderable: reorderable)) - index += 1 - } - - if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack { - entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) - } - - if hasSettings { - entries.append(.settings(theme, strings, expanded)) - } - return entries -} - -func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, strings: PresentationStrings, reactions: [String], animatedEmojiStickers: [String: [StickerPackItem]], expanded: Bool) -> [ChatMediaInputPanelEntry] { - var entries: [ChatMediaInputPanelEntry] = [] - entries.append(.stickersMode(theme, strings, expanded)) - entries.append(.savedGifs(theme, strings, expanded)) - entries.append(.trendingGifs(theme, strings, expanded)) - - for reaction in reactions { - entries.append(.gifEmotion(entries.count, theme, strings, reaction, animatedEmojiStickers[reaction]?.first?.file, expanded)) - } - - return entries -} - -func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, trendingPacks: [FeaturedStickerPackItem], installedPacks: Set, premiumStickers: OrderedItemListView? = nil, cloudPremiumStickers: OrderedItemListView? = nil, trendingIsDismissed: Bool = false, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme, hasPremium: Bool, isPremiumDisabled: Bool, trendingIsPremium: Bool) -> [ChatMediaInputGridEntry] { - var entries: [ChatMediaInputGridEntry] = [] - - if hasSearch && view.lower == nil { - entries.append(.search(theme: theme, strings: strings)) - } - - var stickerPackInfos: [ItemCollectionId: StickerPackCollectionInfo] = [:] - for (id, info, _) in view.collectionInfos { - if let info = info as? StickerPackCollectionInfo { - stickerPackInfos[id] = info - } - } - - if view.lower == nil { - var savedStickerIds = Set() - if let savedStickers = savedStickers, !savedStickers.items.isEmpty { - let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FavoriteStickers.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0) - for i in 0 ..< savedStickers.items.count { - if let item = savedStickers.items[i].contents.get(SavedStickerItem.self) { - savedStickerIds.insert(item.file.fileId.id) - let index = ItemCollectionItemIndex(index: Int32(i), id: item.file.fileId.id) - let stickerItem = StickerPackItem(index: index, file: item.file, indexKeys: []) - if isPremiumDisabled && item.file.isPremiumSticker { - - } else { - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -3, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - } - } - } - } - - let filteredTrending = trendingPacks.filter { !installedPacks.contains($0.info.id) } - if !trendingIsDismissed && !filteredTrending.isEmpty { - entries.append(.trendingList(theme: theme, strings: strings, packs: filteredTrending, isPremium: trendingIsPremium)) - } - - if let recentStickers = recentStickers, !recentStickers.items.isEmpty { - let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0) - var addedCount = 0 - for i in 0 ..< recentStickers.items.count { - if addedCount >= 20 { - break - } - if let item = recentStickers.items[i].contents.get(RecentMediaItem.self), let mediaId = item.media.id { - let file = item.media - - if !savedStickerIds.contains(mediaId.id) { - let index = ItemCollectionItemIndex(index: Int32(i), id: mediaId.id) - let stickerItem = StickerPackItem(index: index, file: file, indexKeys: []) - - if isPremiumDisabled && file.isPremiumSticker { - - } else { - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -2, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - addedCount += 1 - } - } - } - } - } - - var canManagePeerSpecificPack = false - if case .available(_, false) = canInstallPeerSpecificPack { - canManagePeerSpecificPack = true - } - - if peerSpecificPack == nil && canManagePeerSpecificPack { - entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: false)) - } - - if let peerSpecificPack = peerSpecificPack { - let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0) - - for i in 0 ..< peerSpecificPack.items.count { - if let item = peerSpecificPack.items[i] as? StickerPackItem { - let index = ItemCollectionItemIndex(index: Int32(i), id: item.file.fileId.id) - let stickerItem = StickerPackItem(index: index, file: item.file, indexKeys: []) - if isPremiumDisabled && item.file.isPremiumSticker { - - } else { - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: canManagePeerSpecificPack, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - } - } - } - } - - if hasPremium && !isPremiumDisabled { - var existingStickerIds = Set() - let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_PremiumStickers.uppercased(), shortName: "", thumbnail: nil, thumbnailFileId: nil, immediateThumbnailData: nil, hash: 0, count: 0) - - if let premiumStickers = premiumStickers { - for i in 0 ..< premiumStickers.items.count { - if let item = premiumStickers.items[i].contents.get(RecentMediaItem.self) { - let file = item.media - let index = ItemCollectionItemIndex(index: Int32(i), id: file.fileId.id) - let stickerItem = StickerPackItem(index: index, file: file, indexKeys: []) - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - - existingStickerIds.insert(file.fileId.id) - } - } - } - - if let cloudPremiumStickers = cloudPremiumStickers, existingStickerIds.isEmpty { - for i in 0 ..< cloudPremiumStickers.items.count { - if let item = cloudPremiumStickers.items[i].contents.get(RecentMediaItem.self) { - let file = item.media - if !existingStickerIds.contains(file.fileId.id) { - let index = ItemCollectionItemIndex(index: Int32(i), id: file.fileId.id) - let stickerItem = StickerPackItem(index: index, file: file, indexKeys: []) - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - - existingStickerIds.insert(file.fileId.id) - } - } - } - } - } - } - - for entry in view.entries { - if let item = entry.item as? StickerPackItem { - if isPremiumDisabled && item.file.isPremiumSticker { - - } else { - entries.append(.sticker(index: entry.index, stickerItem: item, stickerPackInfo: stickerPackInfos[entry.index.collectionId], canManagePeerSpecificPack: false, maybeManageable: hasAccessories, theme: theme, isLocked: item.file.isPremiumSticker && !hasPremium)) - } - } - } - - if view.higher == nil { - if peerSpecificPack == nil, case .available(_, true) = canInstallPeerSpecificPack { - entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: true)) - } - } - return entries -} - -enum StickerPacksCollectionPosition: Equatable { - case initial - case scroll(aroundIndex: ItemCollectionViewEntryIndex?) - case navigate(index: ItemCollectionViewEntryIndex?, collectionId: ItemCollectionId?) - - static func ==(lhs: StickerPacksCollectionPosition, rhs: StickerPacksCollectionPosition) -> Bool { - switch lhs { - case .initial: - if case .initial = rhs { - return true - } else { - return false - } - case let .scroll(lhsAroundIndex): - if case let .scroll(rhsAroundIndex) = rhs, lhsAroundIndex == rhsAroundIndex { - return true - } else { - return false - } - case .navigate: - return false - } - } -} - -enum StickerPacksCollectionUpdate { - case initial - case generic - case scroll - case navigate(ItemCollectionViewEntryIndex?, ItemCollectionId?) -} - -func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { - switch position { - case let .scroll(index): - if let index = index, index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue || index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { - return .scroll(aroundIndex: nil) - } - default: - break - } - return position -} - -enum ChatMediaInputPaneType { - case gifs - case stickers -} - -struct ChatMediaInputPaneArrangement { - let panes: [ChatMediaInputPaneType] - let currentIndex: Int - let indexTransition: CGFloat - - func withIndexTransition(_ indexTransition: CGFloat) -> ChatMediaInputPaneArrangement { - return ChatMediaInputPaneArrangement(panes: self.panes, currentIndex: currentIndex, indexTransition: indexTransition) - } - - func withCurrentIndex(_ currentIndex: Int) -> ChatMediaInputPaneArrangement { - return ChatMediaInputPaneArrangement(panes: self.panes, currentIndex: currentIndex, indexTransition: self.indexTransition) - } -} - -final class CollectionListContainerNode: ASDisplayNode { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - for subview in self.view.subviews { - if let result = subview.hitTest(point.offsetBy(dx: -subview.frame.minX, dy: -subview.frame.minY), with: event) { - return result - } - } - return nil - } -} - -final class ChatMediaInputNode: ChatInputNode { - private let context: AccountContext - private let peerId: PeerId? - private let controllerInteraction: ChatControllerInteraction - private let gifPaneIsActiveUpdated: (Bool) -> Void - - private var inputNodeInteraction: ChatMediaInputNodeInteraction! - private var trendingInteraction: TrendingPaneInteraction? - - private let collectionListPanel: ASDisplayNode - private let collectionListSeparator: ASDisplayNode - private let collectionListContainer: CollectionListContainerNode - - private weak var peekController: PeekController? - - private let disposable = MetaDisposable() - - private let listView: ListView - private let gifListView: ListView - private var searchContainerNode: PaneSearchContainerNode? - private let searchContainerNodeLoadedDisposable = MetaDisposable() - - private let paneClippingContainer: ASDisplayNode - private let panesBackgroundNode: ASDisplayNode - private let stickerPane: ChatMediaInputStickerPane - private var animatingStickerPaneOut = false - private let gifPane: ChatMediaInputGifPane - private var animatingGifPaneOut = false - private var animatingTrendingPaneOut = false - - private var panRecognizer: UIPanGestureRecognizer? - - private let itemCollectionsViewPosition = Promise() - private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition? - private var currentView: ItemCollectionsView? - private let dismissedPeerSpecificStickerPack = Promise() - - private var scrollingStickerPacksListPromise = ValuePromise(false) - private var scrollingStickersGridPromise = ValuePromise(false) - private var previewingStickersPromise = ValuePromise(false) - private var choosingSticker: Signal { - return combineLatest(self.scrollingStickerPacksListPromise.get(), self.scrollingStickersGridPromise.get(), self.previewingStickersPromise.get()) - |> map { scrollingStickerPacksList, scrollingStickersGrid, previewingStickers -> Bool in - return scrollingStickerPacksList || scrollingStickersGrid || previewingStickers - } - |> distinctUntilChanged - } - private var choosingStickerDisposable: Disposable? - - private var panelFocusScrollToIndex: Int? - private var panelFocusInitialPosition: CGPoint? - private let panelIsFocusedPromise = ValuePromise(false) - private var panelIsFocused: Bool = false { - didSet { - self.panelIsFocusedPromise.set(self.panelIsFocused) - } - } - private var panelFocusTimer: SwiftSignalKit.Timer? - private var lastReorderItemIndex: Int? - - var requestDisableStickerAnimations: ((Bool) -> Void)? - - private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, LayoutMetrics, DeviceMetrics, Bool, Bool)? - private var paneArrangement: ChatMediaInputPaneArrangement - private var initializedArrangement = false - - private var theme: PresentationTheme - private var strings: PresentationStrings - private var fontSize: PresentationFontSize - private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> - - private let _ready = Promise() - private var didSetReady = false - override var ready: Signal { - return self._ready.get() - } - - init(context: AccountContext, peerId: PeerId?, chatLocation: ChatLocation?, controllerInteraction: ChatControllerInteraction, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { - self.context = context - self.peerId = peerId - self.controllerInteraction = controllerInteraction - self.theme = theme - self.strings = strings - self.fontSize = fontSize - self.gifPaneIsActiveUpdated = gifPaneIsActiveUpdated - - self.paneClippingContainer = ASDisplayNode() - self.paneClippingContainer.clipsToBounds = true - - self.panesBackgroundNode = ASDisplayNode() - - self.themeAndStringsPromise = Promise((theme, strings)) - - self.collectionListPanel = ASDisplayNode() - - self.collectionListSeparator = ASDisplayNode() - self.collectionListSeparator.isLayerBacked = true - self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor - - self.collectionListContainer = CollectionListContainerNode() - - 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 = true - self.listView.accessibilityPageScrolledString = { row, count in - return strings.VoiceOver_ScrollStatus(row, count).string - } - - self.gifListView = ListView() - self.gifListView.useSingleDimensionTouchPoint = true - self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) - self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = true - self.gifListView.accessibilityPageScrolledString = { row, count in - return strings.VoiceOver_ScrollStatus(row, count).string - } - - var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)? - var fixPaneScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void)? - var openGifContextMenuImpl: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? - - self.stickerPane = ChatMediaInputStickerPane(theme: theme, strings: strings, paneDidScroll: { pane, state, transition in - paneDidScrollImpl?(pane, state, transition) - }, fixPaneScroll: { pane, state in - fixPaneScrollImpl?(pane, state) - }) - self.gifPane = ChatMediaInputGifPane(context: context, theme: theme, strings: strings, controllerInteraction: controllerInteraction, paneDidScroll: { pane, state, transition in - paneDidScrollImpl?(pane, state, transition) - }, fixPaneScroll: { pane, state in - fixPaneScrollImpl?(pane, state) - }, openGifContextMenu: { file, sourceNode, sourceRect, gesture, isSaved in - openGifContextMenuImpl?(file, sourceNode, sourceRect, gesture, isSaved) - }) - - var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)? - - self.paneArrangement = ChatMediaInputPaneArrangement(panes: [.gifs, .stickers], currentIndex: 1, indexTransition: 0.0) - - super.init() - - self.stickerPane.beganScrolling = { [weak self] in - self?.scrollingStickersGridPromise.set(true) - } - self.stickerPane.endedScrolling = { [weak self] in - self?.scrollingStickersGridPromise.set(false) - } - - 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.engine.stickers.reorderStickerPacks(namespace: Namespaces.ItemCollection.CloudStickerPacks, itemIds: currentIds) - |> 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: 2.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 - if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { - strongSelf.setCurrentPane(.gifs, transition: .animated(duration: 0.25, curve: .spring)) - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { - strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( - context: strongSelf.context, - highlightedPackId: nil, - sendSticker: { - fileReference, sourceNode, sourceRect in - if let strongSelf = self { - return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) - } else { - return false - } - } - )) - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue { - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) - strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) - strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue { - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) - strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) - } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue { - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) - strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) - } else { - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) - for (id, _, _) in currentView.collectionInfos { - if id.namespace == collectionId.namespace { - if id == collectionId { - let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id) - strongSelf.currentStickerPacksCollectionPosition = .navigate(index: itemIndex, collectionId: nil) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex, collectionId: nil))) - break - } - index += 1 - } - } - } - } - }, navigateBackToStickers: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) - }, setGifMode: { [weak self] mode in - guard let strongSelf = self else { - return - } - strongSelf.gifPane.setMode(mode: mode) - strongSelf.inputNodeInteraction.highlightedGifMode = strongSelf.gifPane.mode - - strongSelf.gifListView.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { - itemNode.updateIsHighlighted() - } - } - }, openSettings: { [weak self] in - if let strongSelf = self { - let controller = installedStickerPacksController(context: context, mode: .modal) - controller.navigationPresentation = .modal - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - } - }, openTrending: { [weak self] packId in - if let strongSelf = self { - strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( - context: strongSelf.context, - highlightedPackId: packId, - sendSticker: { - fileReference, sourceNode, sourceRect in - if let strongSelf = self { - return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) - } else { - return false - } - } - )) - } - }, dismissTrendingPacks: { _ in - let _ = (context.account.viewTracker.featuredStickerPacks() - |> take(1) - |> deliverOnMainQueue).start(next: { packs in - let ids = packs.map { $0.info.id.id } - let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: ids).start() - }) - }, toggleSearch: { [weak self] value, searchMode, query in - if let strongSelf = self { - if let searchMode = searchMode, value { - var searchContainerNode: PaneSearchContainerNode? - if let current = strongSelf.searchContainerNode { - searchContainerNode = current - } else { - searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.inputNodeInteraction, mode: searchMode, trendingGifsPromise: strongSelf.gifPane.trendingPromise, cancel: { - self?.searchContainerNode?.deactivate() - self?.inputNodeInteraction.toggleSearch(false, nil, "") - }) - searchContainerNode?.openGifContextMenu = { file, sourceNode, sourceRect, gesture, isSaved in - self?.openGifContextMenu(file: file, sourceNode: sourceNode, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved) - } - strongSelf.searchContainerNode = searchContainerNode - if !query.isEmpty { - DispatchQueue.main.async { - searchContainerNode?.updateQuery(query) - } - } - } - if let searchContainerNode = searchContainerNode { - strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready - |> deliverOnMainQueue).start(next: { - if let strongSelf = self { - strongSelf.controllerInteraction.updateInputMode { current in - switch current { - case let .media(mode, _, focused): - return .media(mode: mode, expanded: .search(searchMode), focused: focused) - default: - return current - } - } - } - })) - } - } else { - strongSelf.controllerInteraction.updateInputMode { current in - switch current { - case let .media(mode, _, focused): - return .media(mode: mode, expanded: nil, focused: focused) - default: - return current - } - } - } - } - }, openPeerSpecificSettings: { [weak self] in - guard let peerId = peerId, peerId.namespace == Namespaces.Peer.CloudChannel else { - return - } - - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StickerPack(id: peerId)) - |> deliverOnMainQueue).start(next: { info in - guard let strongSelf = self else { - return - } - strongSelf.controllerInteraction.presentController(groupStickerPackSetupController(context: context, peerId: peerId, currentPackInfo: info), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }) - }, dismissPeerSpecificSettings: { [weak self] in - self?.dismissPeerSpecificPackSetup() - }, clearRecentlyUsedStickers: { [weak self] in - if let strongSelf = self { - let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: strongSelf.theme, fontSize: strongSelf.fontSize)) - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: strongSelf.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - let _ = context.engine.stickers.clearRecentlyUsedStickers().start() - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.controllerInteraction.presentController(actionSheet, nil) - } - }) - - getItemIsPreviewedImpl = { [weak self] item in - if let strongSelf = self { - return strongSelf.inputNodeInteraction.previewedStickerPackItem == .pack(item.file) - } - return false - } - - self.panesBackgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0) - - self.addSubnode(self.paneClippingContainer) - self.paneClippingContainer.addSubnode(self.panesBackgroundNode) - self.collectionListPanel.addSubnode(self.listView) - self.collectionListPanel.addSubnode(self.gifListView) - self.gifListView.isHidden = true - self.collectionListContainer.addSubnode(self.collectionListPanel) - self.collectionListContainer.addSubnode(self.collectionListSeparator) - self.addSubnode(self.collectionListContainer) - - let itemCollectionsView = self.itemCollectionsViewPosition.get() - |> distinctUntilChanged - |> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in - let orderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers] - let namespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] - switch position { - case .initial: - var firstTime = true - return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: namespaces, aroundIndex: nil, count: 50) - |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in - let update: StickerPacksCollectionUpdate - if firstTime { - firstTime = false - update = .initial - } else { - update = .generic - } - return (view, update) - } - case let .scroll(aroundIndex): - var firstTime = true - return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: namespaces, aroundIndex: aroundIndex, count: 300) - |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in - let update: StickerPacksCollectionUpdate - if firstTime { - firstTime = false - update = .scroll - } else { - update = .generic - } - return (view, update) - } - case let .navigate(index, collectionId): - var firstTime = true - return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: namespaces, aroundIndex: index, count: 300) - |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in - let update: StickerPacksCollectionUpdate - if firstTime { - firstTime = false - update = .navigate(index, collectionId) - } else { - update = .generic - } - return (view, update) - } - } - } - self.inputNodeInteraction.stickerSettings = self.controllerInteraction.stickerSettings - - let previousEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], [], [])) - - let inputNodeInteraction = self.inputNodeInteraction! - let peerSpecificPack: Signal<(PeerSpecificPackData?, CanInstallPeerSpecificPack), NoError> - if let peerId = peerId { - self.dismissedPeerSpecificStickerPack.set( - context.engine.peers.getOpaqueChatInterfaceState(peerId: peerId, threadId: nil) - |> map { opaqueState -> Bool in - guard let opaqueState = opaqueState else { - return false - } - let interfaceState = ChatInterfaceState.parse(opaqueState) - - if interfaceState.messageActionsState.closedPeerSpecificPackSetup { - return true - } - return false - } - ) - peerSpecificPack = combineLatest(context.engine.peers.peerSpecificStickerPack(peerId: peerId), context.account.postbox.multiplePeersView([peerId]), self.dismissedPeerSpecificStickerPack.get()) - |> map { packData, peersView, dismissedPeerSpecificPack -> (PeerSpecificPackData?, CanInstallPeerSpecificPack) in - if let peer = peersView.peers[peerId] { - var canInstall: CanInstallPeerSpecificPack = .none - if packData.canSetup { - canInstall = .available(peer: peer, dismissed: dismissedPeerSpecificPack) - } - if let (info, items) = packData.packInfo { - return (PeerSpecificPackData(peer: peer, info: info, items: items), canInstall) - } else { - return (nil, canInstall) - } - } - return (nil, .none) - } - } else { - peerSpecificPack = .single((nil, .none)) - } - - let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in - guard let info = info as? StickerPackCollectionInfo else { - return - } - let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false) - |> mapToSignal { result -> Signal in - switch result { - case let .result(info, items, installed): - if installed { - return .complete() - } else { - return context.engine.stickers.addStickerPackInteractively(info: info, items: items) - } - case .fetching: - break - case .none: - break - } - return .complete() - } - |> deliverOnMainQueue).start(completed: { - guard let strongSelf = self else { - return - } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.controllerInteraction.presentController(OverlayStatusController(theme: presentationData.theme, type: .success), nil) - }) - }, openPack: { [weak self] info in - guard let strongSelf = self, let info = info as? StickerPackCollectionInfo else { - return - } - strongSelf.view.window?.endEditing(true) - let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) - let controller = StickerPackScreen(context: strongSelf.context, updatedPresentationData: strongSelf.controllerInteraction.updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { fileReference, sourceNode, sourceRect in - if let strongSelf = self { - return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) - } else { - return false - } - }) - strongSelf.controllerInteraction.presentController(controller, nil) - }, getItemIsPreviewed: { item in - return getItemIsPreviewedImpl?(item) ?? false - }, openSearch: { - }) - self.trendingInteraction = trendingInteraction - - let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) - |> map { appConfiguration -> [String] in - let defaultReactions: [String] = ["👍", "👎", "😍", "😂", "😯", "😕", "😢", "😡", "💪", "👏", "🙈", "😒"] - - guard let data = appConfiguration.data, let emojis = data["gif_search_emojies"] as? [String] else { - return defaultReactions - } - return emojis - } - |> 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 item 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 premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - - let previousView = Atomic(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), - temporaryPackOrder.get(), - animatedEmojiStickers, - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), - context.engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.featuredStickersConfiguration, id: ValueBoxKey(length: 0))) - ) - |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks, temporaryPackOrder, animatedEmojiStickers, accountPeer, featuredStickersConfiguration -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in - let (view, viewUpdate) = viewAndUpdate - let previous = previousView.swap(view) - var update = viewUpdate - if previous === view { - update = .generic - } - let (theme, strings) = themeAndStrings - - var savedStickers: OrderedItemListView? - var recentStickers: OrderedItemListView? - var premiumStickers: OrderedItemListView? - var cloudPremiumStickers: OrderedItemListView? - for orderedView in view.orderedItemListsViews { - if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { - recentStickers = orderedView - } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { - savedStickers = orderedView - } else if orderedView.collectionId == Namespaces.OrderedItemList.PremiumStickers { - premiumStickers = orderedView - } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudPremiumStickers { - cloudPremiumStickers = orderedView - } - } - - var installedPacks = Set() - for info in view.collectionInfos { - installedPacks.insert(info.0) - } - - var trendingIsDismissed = false - if let dismissedTrendingStickerPacks = dismissedTrendingStickerPacks, Set(trendingPacks.map({ $0.info.id.id })) == Set(dismissedTrendingStickerPacks) { - trendingIsDismissed = true - } - - let hasPremium = accountPeer?.isPremium ?? false - let featuredStickersConfiguration = featuredStickersConfiguration?.get(FeaturedStickersConfiguration.self) - - - var hasPremiumStickers = false - if hasPremium { - if let premiumStickers = premiumStickers, !premiumStickers.items.isEmpty { - hasPremiumStickers = true - } else if let cloudPremiumStickers = cloudPremiumStickers, !cloudPremiumStickers.items.isEmpty { - hasPremiumStickers = true - } - } - - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, temporaryPackOrder: temporaryPackOrder, trendingIsDismissed: trendingIsDismissed, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, strings: strings, hasPremiumStickers: hasPremiumStickers, cloudPremiumStickers: hasPremium ? cloudPremiumStickers : nil, expanded: panelExpanded, reorderable: true) - let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, strings: strings, reactions: reactions, animatedEmojiStickers: animatedEmojiStickers, expanded: panelExpanded) - var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, installedPacks: installedPacks, premiumStickers: premiumStickers, cloudPremiumStickers: cloudPremiumStickers, trendingIsDismissed: trendingIsDismissed, strings: strings, theme: theme, hasPremium: hasPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled, trendingIsPremium: featuredStickersConfiguration?.isPremium ?? false) - - if view.higher == nil { - var hasTopSeparator = true - if gridEntries.count == 1, case .search = gridEntries[0] { - hasTopSeparator = false - } - - var index = 0 - for item in trendingPacks { - if !installedPacks.contains(item.info.id) { - gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: hasTopSeparator))) - hasTopSeparator = true - index += 1 - } - } - } - - let (previousPanelEntries, previousGifPaneEntries, previousGridEntries) = previousEntries.swap((panelEntries, gifPaneEntries, gridEntries)) - return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction, scrollToItem: nil), preparedChatMediaInputPanelEntryTransition(context: context, from: previousGifPaneEntries, to: gifPaneEntries, inputNodeInteraction: inputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) - } - - self.disposable.set((transitions - |> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, gifPaneTransition, panelFirstTime, gridTransition, gridFirstTime) in - if let strongSelf = self { - strongSelf.currentView = view - strongSelf.enqueuePanelTransition(panelTransition, firstTime: panelFirstTime, thenGridTransition: gridTransition, gridFirstTime: gridFirstTime) - strongSelf.enqueueGifPanelTransition(gifPaneTransition, firstTime: false) - if !strongSelf.initializedArrangement { - strongSelf.initializedArrangement = true - let currentPane = strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] - if currentPane != strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] { - strongSelf.setCurrentPane(currentPane, transition: .immediate) - } - } - } - })) - - self.stickerPane.gridNode.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self { - var topVisibleCollectionId: ItemCollectionId? - - if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection { - topVisibleCollectionId = topVisibleSection.collectionId - } else if let topVisible = visibleItems.topVisible { - if let _ = topVisible.1 as? StickerPaneTrendingListGridItem { - topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) - } else if let item = topVisible.1 as? ChatMediaInputStickerGridItem { - topVisibleCollectionId = item.index.collectionId - } else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem { - topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0) - } - } - if let collectionId = topVisibleCollectionId { - if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId && strongSelf.inputNodeInteraction.highlightedItemCollectionId?.namespace != ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { - strongSelf.setHighlightedItemCollectionId(collectionId) - } - } - - if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom { - if topIndex <= 10 && currentView.lower != nil { - if let topItem = topItem as? ChatMediaInputStickerGridItem { - let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: topItem.index)) - if strongSelf.currentStickerPacksCollectionPosition != position { - strongSelf.currentStickerPacksCollectionPosition = position - strongSelf.itemCollectionsViewPosition.set(.single(position)) - } - } - } else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil { - var position: StickerPacksCollectionPosition? - if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem { - position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index)) - } - - if let position = position, strongSelf.currentStickerPacksCollectionPosition != position { - strongSelf.currentStickerPacksCollectionPosition = position - strongSelf.itemCollectionsViewPosition.set(.single(position)) - } - } - } - } - } - - self.currentStickerPacksCollectionPosition = .initial - self.itemCollectionsViewPosition.set(.single(.initial)) - - self.stickerPane.inputNodeInteraction = self.inputNodeInteraction - self.gifPane.inputNodeInteraction = self.inputNodeInteraction - - paneDidScrollImpl = { [weak self] pane, state, transition in - self?.updatePaneDidScroll(pane: pane, state: state, transition: transition) - } - - fixPaneScrollImpl = { [weak self] pane, state in - self?.fixPaneScroll(pane: pane, state: state) - } - - openGifContextMenuImpl = { [weak self] file, sourceNode, sourceRect, gesture, isSaved in - self?.openGifContextMenu(file: file, sourceNode: sourceNode, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved) - } - - self.listView.beganInteractiveDragging = { [weak self] position in - if let strongSelf = self { - strongSelf.stopCollapseTimer() - - strongSelf.scrollingStickerPacksListPromise.set(true) - - var position = position - var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) - if index == nil { - position.y += 10.0 - index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) - } - if let index = index { - strongSelf.panelFocusScrollToIndex = index - strongSelf.panelFocusInitialPosition = position - } - strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: true)) - } else { - return (inputTextState, inputMode) - } - } - } - } - - self.listView.endedInteractiveDragging = { [weak self] position in - if let strongSelf = self { - strongSelf.panelFocusInitialPosition = position - } - } - - self.listView.didEndScrolling = { [weak self] decelerated in - if let strongSelf = self { - if decelerated { - strongSelf.panelFocusScrollToIndex = nil - strongSelf.panelFocusInitialPosition = nil - } - strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 2.5) - - strongSelf.scrollingStickerPacksListPromise.set(false) - } - } - - self.gifListView.beganInteractiveDragging = { [weak self] position in - if let strongSelf = self { - strongSelf.stopCollapseTimer() - var position = position - var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) - if index == nil { - position.y += 10.0 - index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) - } - if let index = index { - strongSelf.panelFocusScrollToIndex = index - strongSelf.panelFocusInitialPosition = position - } - strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: true)) - } else { - return (inputTextState, inputMode) - } - } - } - } - - self.gifListView.endedInteractiveDragging = { [weak self] position in - if let strongSelf = self { - strongSelf.panelFocusInitialPosition = position - } - } - - self.gifListView.didEndScrolling = { [weak self] decelerated in - if let strongSelf = self { - if decelerated { - strongSelf.panelFocusScrollToIndex = nil - strongSelf.panelFocusInitialPosition = nil - } - strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 2.5) - } - } - - self.choosingStickerDisposable = (self.choosingSticker - |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self { - strongSelf.controllerInteraction.updateChoosingSticker(value) - } - }) - } - - deinit { - self.disposable.dispose() - self.choosingStickerDisposable?.dispose() - self.searchContainerNodeLoadedDisposable.dispose() - self.panelFocusTimer?.invalidate() - } - - private func updateIsFocused(_ isExpanded: Bool) { - guard self.panelIsFocused != isExpanded else { - return - } - - self.panelIsFocused = isExpanded - self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring)) - } - - private func startCollapseTimer(timeout: Double) { - self.panelFocusTimer?.invalidate() - - let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in - self?.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - }, queue: Queue.mainQueue()) - self.panelFocusTimer = timer - 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 { - canSaveGif = true - } else { - canSaveGif = false - } - - let _ = (self.context.engine.stickers.isGifSaved(id: file.file.media.fileId) - |> deliverOnMainQueue).start(next: { [weak self] isGifSaved in - guard let strongSelf = self else { - return - } - var isGifSaved = isGifSaved - if !canSaveGif { - isGifSaved = false - } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) - - let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in - }, baseNavigationController: nil) - gallery.setHintWillBePresentedInPreviewingContext(true) - - var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: strongSelf.strings.MediaPicker_Send, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - if isSaved { - let _ = self?.controllerInteraction.sendGif(file.file, sourceNode.view, sourceRect, false, false) - } else if let (collection, result) = file.contextResult { - let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode.view, sourceRect, false) - } - }))) - - if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _, _) = strongSelf.validLayout { - var isScheduledMessages = false - if case .scheduledMessages = interfaceState.subject { - isScheduledMessages = true - } - if !isScheduledMessages { - if case let .peer(peerId) = interfaceState.chatLocation { - if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat { - items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - if isSaved { - let _ = self?.controllerInteraction.sendGif(file.file, sourceNode.view, sourceRect, true, false) - } else if let (collection, result) = file.contextResult { - let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode.view, sourceRect, true) - } - }))) - } - - if isSaved { - items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - - let _ = self?.controllerInteraction.sendGif(file.file, sourceNode.view, sourceRect, false, true) - }))) - } - } - } - } - - if isSaved || isGifSaved { - items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) - }, action: { _, f in - f(.dismissWithoutContent) - - guard let strongSelf = self else { - return - } - let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.file.media.fileId).start() - }))) - } else if canSaveGif && !isGifSaved { - items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Preview_SaveGif, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.dismissWithoutContent) - - guard let strongSelf = self else { - return - } - - let context = strongSelf.context - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controllerInteraction = strongSelf.controllerInteraction - let _ = (toggleGifSaved(account: context.account, fileReference: file.file, saved: true) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .generic: - controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - case let .limitExceeded(limit, premiumLimit): - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - let text: String - if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { - text = presentationData.strings.Premium_MaxSavedGifsFinalText - } else { - text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string - } - controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - if case .info = action { - let controller = PremiumIntroScreen(context: context, source: .savedGifs) - controllerInteraction.navigationController()?.pushViewController(controller) - return true - } - return false - }), nil) - } - }) - }))) - } - - let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - strongSelf.controllerInteraction.presentGlobalOverlayController(contextController, nil) - }) - } - - private func updateThemeAndStrings(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings) { - if self.theme !== theme || self.strings !== strings { - self.theme = theme - self.strings = strings - - self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor - self.panesBackgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0) - - self.searchContainerNode?.updateThemeAndStrings(theme: theme, strings: strings) - - self.stickerPane.updateThemeAndStrings(theme: theme, strings: strings) - self.gifPane.updateThemeAndStrings(theme: theme, strings: strings) - - self.themeAndStringsPromise.set(.single((theme, strings))) - } - } - - override func didLoad() { - super.didLoad() - - self.view.disablesInteractiveTransitionGestureRecognizer = true - let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in - if let strongSelf = self { - let panes: [ASDisplayNode] - if let searchContainerNode = strongSelf.searchContainerNode { - panes = [] - - if let (itemNode, item) = searchContainerNode.itemAt(point: point.offsetBy(dx: -searchContainerNode.frame.minX, dy: -searchContainerNode.frame.minY)) { - if let item = item as? StickerPreviewPeekItem { - return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) - |> deliverOnMainQueue - |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in - if let strongSelf = self { - var menuItems: [ContextMenuItem] = [] - if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _, _) = strongSelf.validLayout { - var isScheduledMessages = false - if case .scheduledMessages = interfaceState.subject { - isScheduledMessages = true - } - if !isScheduledMessages { - if case let .peer(peerId) = interfaceState.chatLocation { - if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, animationNode.view, animationNode.bounds, nil, []) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, imageNode.view, imageNode.bounds, nil, []) - } - } - f(.default) - }))) - } - - if interfaceState.chatLocation.threadId == nil { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, animationNode.view, animationNode.bounds, nil, []) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, imageNode.view, imageNode.bounds, nil, []) - } - } - f(.default) - }))) - } - } - } - } - menuItems.append( - .action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) - - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let _ = (strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .generic: - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) - case let .limitExceeded(limit, premiumLimit): - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) - let text: String - if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { - text = strongSelf.strings.Premium_MaxFavedStickersFinalText - } else { - text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string - } - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: strongSelf.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { [weak self] action in - if let strongSelf = self { - if case .info = action { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - return true - } - } - return false - }), nil) - } - }) - } - })) - ) - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - - if let strongSelf = self { - loop: for attribute in item.file.attributes { - switch attribute { - case let .Sticker(_, packReference, _): - if let packReference = packReference { - let controller = StickerPackScreen(context: strongSelf.context, updatedPresentationData: strongSelf.controllerInteraction.updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in - if let strongSelf = self { - return strongSelf.controllerInteraction.sendSticker(file, false, false, nil, false, sourceNode, sourceRect, nil, []) - } else { - return false - } - }) - - strongSelf.controllerInteraction.navigationController()?.view.window?.endEditing(true) - strongSelf.controllerInteraction.presentController(controller, nil) - } - break loop - default: - break - } - } - } - }))) - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in - guard let strongSelf = self else { - return - } - let controller = PremiumIntroScreen(context: strongSelf.context, source: .stickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - })) - } else { - return nil - } - } - } else if let _ = item as? FileMediaReference { - return nil - } - } - } else { - panes = [strongSelf.gifPane, strongSelf.stickerPane] - } - let panelPoint = strongSelf.view.convert(point, to: strongSelf.collectionListPanel.view) - if panelPoint.y < strongSelf.collectionListPanel.frame.maxY { - return .single(nil) - } - - for pane in panes { - if pane.supernode != nil, pane.frame.contains(point) { - if let pane = pane as? ChatMediaInputGifPane { - if let (_, _, _) = pane.fileAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) { - return nil - } - } else if pane is ChatMediaInputStickerPane || pane is ChatMediaInputTrendingPane { - var itemNodeAndItem: (ASDisplayNode, StickerPackItem)? - if let pane = pane as? ChatMediaInputStickerPane { - itemNodeAndItem = pane.itemAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) - } else if let pane = pane as? ChatMediaInputTrendingPane { - itemNodeAndItem = pane.itemAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) - } - - if let (itemNode, item) = itemNodeAndItem { - let accountPeerId = strongSelf.context.account.peerId - return combineLatest( - strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId), - strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in - var hasPremium = false - if case let .user(user) = peer, user.isPremium { - hasPremium = true - } - return hasPremium - } - ) - |> deliverOnMainQueue - |> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in - if let strongSelf = self { - var menuItems: [ContextMenuItem] = [] - if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _, _) = strongSelf.validLayout { - var isScheduledMessages = false - if case .scheduledMessages = interfaceState.subject { - isScheduledMessages = true - } - if !isScheduledMessages { - if case let .peer(peerId) = interfaceState.chatLocation { - if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, animationNode.view, animationNode.bounds, nil, []) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, imageNode.view, imageNode.bounds, nil, []) - } - } - f(.default) - }))) - } - - if interfaceState.chatLocation.threadId == nil { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, animationNode.view, animationNode.bounds, nil, []) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, imageNode.view, imageNode.bounds, nil, []) - } - } - f(.default) - }))) - } - } - } - } - - menuItems.append( - .action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) - - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let _ = (strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .generic: - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) - case let .limitExceeded(limit, premiumLimit): - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) - let text: String - if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { - text = strongSelf.strings.Premium_MaxFavedStickersFinalText - } else { - text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string - } - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: strongSelf.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { [weak self] action in - if let strongSelf = self { - if case .info = action { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - return true - } - } - return false - }), nil) - } - }) - } - })) - ) - menuItems.append( - .action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - - if let strongSelf = self { - loop: for attribute in item.file.attributes { - switch attribute { - case let .Sticker(_, packReference, _): - if let packReference = packReference { - let controller = StickerPackScreen(context: strongSelf.context, updatedPresentationData: strongSelf.controllerInteraction.updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in - if let strongSelf = self { - return strongSelf.controllerInteraction.sendSticker(file, false, false, nil, false, sourceNode, sourceRect, nil, []) - } else { - return false - } - }) - - strongSelf.controllerInteraction.navigationController()?.view.window?.endEditing(true) - strongSelf.controllerInteraction.presentController(controller, nil) - } - break loop - default: - break - } - } - } - })) - ) - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in - guard let strongSelf = self else { - return - } - let controller = PremiumIntroScreen(context: strongSelf.context, source: .stickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - })) - } else { - return nil - } - } - } - } - } - } - } - return nil - }, present: { [weak self] content, sourceView, sourceRect in - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceView: { - return (sourceView, sourceRect) - }) - controller.visibilityUpdated = { [weak self] visible in - self?.previewingStickersPromise.set(visible) - self?.requestDisableStickerAnimations?(visible) - self?.simulateUpdateLayout(isVisible: !visible) - } - strongSelf.peekController = controller - strongSelf.controllerInteraction.presentGlobalOverlayController(controller, nil) - return controller - } - return nil - }, updateContent: { [weak self] content in - if let strongSelf = self { - var item: StickerPreviewPeekItem? - if let content = content as? StickerPreviewPeekContent { - item = content.item - } - strongSelf.updatePreviewingItem(item: item, animated: true) - } - }) - peekRecognizer.checkSingleTapActivationAtPoint = { [weak self] point in - guard let strongSelf = self else { - return false - } - - let pane = strongSelf.stickerPane - let panelPoint = strongSelf.view.convert(point, to: strongSelf.collectionListPanel.view) - if panelPoint.y < strongSelf.collectionListPanel.frame.maxY { - return false - } - - if pane.supernode != nil, pane.frame.contains(point) { - let itemNodeAndItem = pane.itemAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) - if let item = itemNodeAndItem?.0 as? ChatMediaInputStickerGridItemNode, item.isLocked == true { - return true - } - } - return false - } - self.view.addGestureRecognizer(peekRecognizer) - let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - self.panRecognizer = panRecognizer - self.view.addGestureRecognizer(panRecognizer) - } - - private func setCurrentPane(_ pane: ChatMediaInputPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) { - if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { - let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs - self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) - let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs - - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) = self.validLayout { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) - self.updateAppearanceTransition(transition: transition) - } - if updatedGifPanelWasActive != previousGifPanelWasActive { - self.gifPaneIsActiveUpdated(updatedGifPanelWasActive) - } - switch pane { - case .gifs: - self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)) - case .stickers: - if let highlightedStickerCollectionId = self.inputNodeInteraction.highlightedStickerItemCollectionId { - self.setHighlightedItemCollectionId(highlightedStickerCollectionId) - } else if let collectionIdHint = collectionIdHint { - self.setHighlightedItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0)) - } - } - } else { - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) = self.validLayout { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) - } - } - } - - private func setHighlightedItemCollectionId(_ collectionId: ItemCollectionId) { - if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { - if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs { - self.inputNodeInteraction.highlightedItemCollectionId = collectionId - } - } else { - self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId - if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers { - self.inputNodeInteraction.highlightedItemCollectionId = collectionId - } - } - - if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue && self.gifListView.isHidden { - self.listView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.bounds.width, y: 0.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak self] completed in - guard let strongSelf = self, completed else { - return - } - strongSelf.listView.isHidden = true - strongSelf.listView.layer.removeAllAnimations() - }) - self.gifListView.layer.removeAllAnimations() - self.gifListView.isHidden = false - self.gifListView.layer.animatePosition(from: CGPoint(x: -self.bounds.width, y: 0.0), to: CGPoint(), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } else if !self.gifListView.isHidden { - self.gifListView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -self.bounds.width, y: 0.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak self] completed in - guard let strongSelf = self, completed else { - return - } - strongSelf.gifListView.isHidden = true - strongSelf.gifListView.layer.removeAllAnimations() - }) - self.listView.layer.removeAllAnimations() - self.listView.isHidden = false - self.listView.layer.animatePosition(from: CGPoint(x: self.bounds.width, y: 0.0), to: CGPoint(), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - - var ensuredNodeVisible = false - var firstVisibleCollectionId: ItemCollectionId? - self.listView.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { - if firstVisibleCollectionId == nil { - firstVisibleCollectionId = itemNode.currentCollectionId - } - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.ensureItemNodeVisible(itemNode) - } - ensuredNodeVisible = true - } - } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.ensureItemNodeVisible(itemNode) - } - ensuredNodeVisible = true - } - } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.ensureItemNodeVisible(itemNode) - } - ensuredNodeVisible = true - } - } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.ensureItemNodeVisible(itemNode) - } - ensuredNodeVisible = true - } - } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.ensureItemNodeVisible(itemNode) - } - ensuredNodeVisible = true - } - } - } - - if let currentView = self.currentView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible { - let targetIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == collectionId }) - let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId }) - if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { - let toRight = targetIndex > firstVisibleIndex - if self.panelIsFocused { - self.panelFocusScrollToIndex = targetIndex - self.panelFocusInitialPosition = nil - self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in - if case let .media(mode, expanded, _) = inputMode { - return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) - } else { - return (inputTextState, inputMode) - } - } - } else { - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil) - } - } - } - } - - private func currentCollectionListPanelOffset() -> CGFloat { - let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in - switch pane { - case .stickers: - return self.stickerPane.collectionListPanelOffset - case .gifs: - return self.gifPane.collectionListPanelOffset - } - } - - let mainOffset = paneOffsets[self.paneArrangement.currentIndex] - if self.paneArrangement.indexTransition.isZero { - return mainOffset - } else { - var sideOffset: CGFloat? - if self.paneArrangement.indexTransition < 0.0 { - if self.paneArrangement.currentIndex != 0 { - sideOffset = paneOffsets[self.paneArrangement.currentIndex - 1] - } - } else { - if self.paneArrangement.currentIndex != paneOffsets.count - 1 { - sideOffset = paneOffsets[self.paneArrangement.currentIndex + 1] - } - } - if let sideOffset = sideOffset { - let interpolator = CGFloat.interpolator() - let value = interpolator(mainOffset, sideOffset, abs(self.paneArrangement.indexTransition)) as! CGFloat - return value - } else { - return mainOffset - } - } - } - - private func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - var value: CGFloat = 1.0 - abs(self.currentCollectionListPanelOffset() / 41.0) - value = min(1.0, max(0.0, value)) - - self.inputNodeInteraction.appearanceTransition = max(0.1, value) - transition.updateAlpha(node: self.listView, alpha: value) - transition.updateAlpha(node: self.gifListView, alpha: value) - self.listView.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } else if let itemNode = itemNode as? ChatMediaInputSettingsItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } - } - self.gifListView.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { - itemNode.updateAppearanceTransition(transition: transition) - } - } - } - - func simulateUpdateLayout(isVisible: Bool) { - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, _, isExpanded) = self.validLayout { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) - } - } - - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { - var searchMode: ChatMediaInputSearchMode? - if let (_, _, _, _, _, _, _, _, interfaceState, _, _, _, _) = self.validLayout, case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { - searchMode = mode - } - - let wasVisible = self.validLayout?.11 ?? false - - self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) - - if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings { - self.updateThemeAndStrings(chatWallpaper: interfaceState.chatWallpaper, theme: interfaceState.theme, strings: interfaceState.strings) - } - - var displaySearch = false - let separatorHeight = UIScreenPixel - let panelHeight: CGFloat - - var isFocused = false - var isExpanded: Bool = false - if case let .media(_, _, focused) = interfaceState.inputMode { - isFocused = focused - } - if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded { - isExpanded = true - switch expanded { - case .content: - panelHeight = maximumHeight - case let .search(mode): - panelHeight = maximumHeight - displaySearch = true - searchMode = mode - } - self.stickerPane.collectionListPanelOffset = 0.0 - self.gifPane.collectionListPanelOffset = 0.0 - self.updateAppearanceTransition(transition: transition) - } else { - panelHeight = standardInputHeight - } - - self.updateIsFocused(isFocused) - - if displaySearch { - if let searchContainerNode = self.searchContainerNode { - let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) - if searchContainerNode.supernode != nil { - transition.updateFrame(node: searchContainerNode, frame: containerFrame) - searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) - } else { - self.searchContainerNode = searchContainerNode - self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer) - searchContainerNode.frame = containerFrame - searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate) - var placeholderNode: PaneSearchBarPlaceholderNode? - let anchorTop = CGPoint(x: 0.0, y: 0.0) - let anchorTopView: UIView = self.view - if let searchMode = searchMode { - switch searchMode { - case .gif: - placeholderNode = self.gifPane.visibleSearchPlaceholderNode - case .sticker: - self.stickerPane.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { - placeholderNode = itemNode - } - } - case .trending: - break - } - } - - searchContainerNode.animateIn(from: placeholderNode, anchorTop: anchorTop, anhorTopView: anchorTopView, transition: transition, completion: { [weak self] in - self?.gifPane.removeFromSupernode() - }) - } - } - } - - let contentVerticalOffset: CGFloat = displaySearch ? -(inputPanelHeight + 41.0) : 0.0 - - let collectionListPanelOffset = self.currentCollectionListPanelOffset() - - transition.updateFrame(node: self.collectionListContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOffset), size: CGSize(width: width, height: max(0.0, 41.0 + UIScreenPixel)))) - transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: CGSize(width: width, height: 41.0))) - transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: CGSize(width: width, height: separatorHeight))) - - self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width) - transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0)) - - self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width) - transition.updatePosition(node: self.gifListView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0)) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0 + 31.0 + 20.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve) - - self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - self.gifListView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - var visiblePanes: [(ChatMediaInputPaneType, CGFloat)] = [] - - var paneIndex = 0 - for pane in self.paneArrangement.panes { - let paneOrigin = CGFloat(paneIndex - self.paneArrangement.currentIndex) * width - self.paneArrangement.indexTransition * width - if paneOrigin.isLess(than: width) && CGFloat(0.0).isLess(than: (paneOrigin + width)) { - visiblePanes.append((pane, paneOrigin)) - } - paneIndex += 1 - } - - for (pane, paneOrigin) in visiblePanes { - let paneFrame = CGRect(origin: CGPoint(x: paneOrigin + leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight)) - switch pane { - case .gifs: - if self.gifPane.supernode == nil { - if !displaySearch { - self.paneClippingContainer.addSubnode(self.gifPane) - if self.searchContainerNode == nil { - self.gifPane.frame = CGRect(origin: CGPoint(x: -width, y: 0.0), size: CGSize(width: width, height: panelHeight)) - } - } - } - if self.gifPane.frame != paneFrame { - self.gifPane.layer.removeAnimation(forKey: "position") - transition.updateFrame(node: self.gifPane, frame: paneFrame) - } - case .stickers: - if self.stickerPane.supernode == nil { - self.paneClippingContainer.addSubnode(self.stickerPane) - self.stickerPane.frame = CGRect(origin: CGPoint(x: width, y: 0.0), size: CGSize(width: width, height: panelHeight)) - } - if self.stickerPane.frame != paneFrame { - self.stickerPane.layer.removeAnimation(forKey: "position") - transition.updateFrame(node: self.stickerPane, frame: paneFrame) - } - } - } - - self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition) - self.trendingInteraction?.itemContext.canPlayMedia = isVisible - self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), deviceMetrics: deviceMetrics, transition: transition) - - if self.gifPane.supernode != nil { - if !visiblePanes.contains(where: { $0.0 == .gifs }) { - if case .animated = transition { - if !self.animatingGifPaneOut { - self.animatingGifPaneOut = true - var toLeft = false - if let index = self.paneArrangement.panes.firstIndex(of: .gifs), index < self.paneArrangement.currentIndex { - toLeft = true - } - transition.animatePosition(node: self.gifPane, to: CGPoint(x: (toLeft ? -width : width) + width / 2.0, y: self.gifPane.layer.position.y), removeOnCompletion: false, completion: { [weak self] value in - if let strongSelf = self, value { - strongSelf.animatingGifPaneOut = false - strongSelf.gifPane.removeFromSupernode() - } - }) - } - } else { - self.animatingGifPaneOut = false - self.gifPane.removeFromSupernode() - } - } - } else { - self.animatingGifPaneOut = false - } - - if self.stickerPane.supernode != nil { - if !visiblePanes.contains(where: { $0.0 == .stickers }) { - if case .animated = transition { - if !self.animatingStickerPaneOut { - self.animatingStickerPaneOut = true - var toLeft = false - if let index = self.paneArrangement.panes.firstIndex(of: .stickers), index < self.paneArrangement.currentIndex { - toLeft = true - } - transition.animatePosition(node: self.stickerPane, to: CGPoint(x: (toLeft ? -width : width) + width / 2.0, y: self.stickerPane.layer.position.y), removeOnCompletion: false, completion: { [weak self] value in - if let strongSelf = self, value { - strongSelf.animatingStickerPaneOut = false - strongSelf.stickerPane.removeFromSupernode() - } - }) - } - } else { - self.animatingStickerPaneOut = false - self.stickerPane.removeFromSupernode() - } - } - } else { - self.animatingStickerPaneOut = false - } - - if !displaySearch, let searchContainerNode = self.searchContainerNode { - self.searchContainerNode = nil - self.searchContainerNodeLoadedDisposable.set(nil) - - var paneIsEmpty = false - var placeholderNode: PaneSearchBarPlaceholderNode? - if let searchMode = searchMode { - switch searchMode { - case .gif: - placeholderNode = self.gifPane.visibleSearchPlaceholderNode - paneIsEmpty = placeholderNode != nil - case .sticker: - paneIsEmpty = true - self.stickerPane.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { - placeholderNode = itemNode - } - if let _ = itemNode as? ChatMediaInputStickerGridItemNode { - paneIsEmpty = false - } - } - case .trending: - break - } - } - if let placeholderNode = placeholderNode { - placeholderNode.isHidden = false - searchContainerNode.animateOut(to: placeholderNode, animateOutSearchBar: !paneIsEmpty, transition: transition, completion: { [weak searchContainerNode] in - searchContainerNode?.removeFromSupernode() - }) - } else { - transition.updateAlpha(node: searchContainerNode, alpha: 0.0, completion: { [weak searchContainerNode] _ in - searchContainerNode?.removeFromSupernode() - }) - } - } - - if let panRecognizer = self.panRecognizer, panRecognizer.isEnabled != !displaySearch { - panRecognizer.isEnabled = !displaySearch - } - - if isVisible && !wasVisible { - transition.updateFrame(node: self.gifPane, frame: self.gifPane.frame, force: true, completion: { [weak self] _ in - self?.gifPane.initializeIfNeeded() - }) - } - - self.updatePaneClippingContainer(size: CGSize(width: width, height: panelHeight), offset: self.currentCollectionListPanelOffset(), transition: transition) - - transition.updateFrame(node: self.panesBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: panelHeight))) - - return (standardInputHeight, max(0.0, panelHeight - standardInputHeight)) - } - - private func enqueuePanelTransition(_ transition: ChatMediaInputPanelTransition, firstTime: Bool, thenGridTransition gridTransition: ChatMediaInputGridTransition, gridFirstTime: Bool) { - var options = ListViewDeleteAndInsertOptions() - if firstTime { - options.insert(.Synchronous) - options.insert(.LowLatency) - } else { - options.insert(.AnimateInsertion) - } - - var scrollToItem: ListViewScrollToItem? - if self.paneArrangement.currentIndex == 1 { - if let targetIndex = self.panelFocusScrollToIndex, !self.listView.isReordering { - var position: ListViewScrollPosition - if self.panelIsFocused { - if let initialPosition = self.panelFocusInitialPosition { - position = .top(96.0 + (initialPosition.y - self.listView.frame.height / 2.0) * 0.5) - } else { - position = .top(96.0) - } - } else { - if let initialPosition = self.panelFocusInitialPosition { - position = .top(self.listView.frame.height / 2.0 + 96.0 + (initialPosition.y - self.listView.frame.height / 2.0)) - } else { - position = .top(self.listView.frame.height / 2.0 + 96.0) - } - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - } - 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: transition.updateOpaqueState, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) - if !strongSelf.didSetReady { - strongSelf.didSetReady = true - strongSelf._ready.set(.single(Void())) - } - } - }) - } - - private func enqueueGifPanelTransition(_ transition: ChatMediaInputPanelTransition, firstTime: Bool) { - var options = ListViewDeleteAndInsertOptions() - if firstTime { - options.insert(.Synchronous) - options.insert(.LowLatency) - } else { - options.insert(.AnimateInsertion) - } - - var scrollToItem: ListViewScrollToItem? - if self.paneArrangement.currentIndex == 0 { - if let targetIndex = self.panelFocusScrollToIndex { - var position: ListViewScrollPosition - if self.panelIsFocused { - if let initialPosition = self.panelFocusInitialPosition { - position = .top(96.0 + (initialPosition.y - self.gifListView.frame.height / 2.0) * 0.5) - } else { - position = .top(96.0) - } - } else { - if let initialPosition = self.panelFocusInitialPosition { - position = .top(self.gifListView.frame.height / 2.0 + 96.0 + (initialPosition.y - self.gifListView.frame.height / 2.0)) - } else { - position = .top(self.gifListView.frame.height / 2.0 + 96.0) - } - self.panelFocusScrollToIndex = nil - self.panelFocusInitialPosition = nil - } - scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) - } - } - - self.gifListView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: nil, completion: { _ in - }) - } - - private func enqueueGridTransition(_ transition: ChatMediaInputGridTransition, firstTime: Bool) { - var itemTransition: ContainedViewLayoutTransition = .immediate - if transition.animated { - itemTransition = .animated(duration: 0.3, curve: .spring) - } - self.stickerPane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset, updateOpaqueState: transition.updateOpaqueState), completion: { _ in }) - } - - private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) { - if self.inputNodeInteraction.previewedStickerPackItem != item { - self.inputNodeInteraction.previewedStickerPackItem = item - - self.stickerPane.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputStickerGridItemNode { - itemNode.updatePreviewing(animated: animated) - } - } - - self.searchContainerNode?.contentNode.updatePreviewing(animated: animated) - } - } - - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - switch recognizer.state { - case .began: - if self.animatingGifPaneOut { - self.animatingGifPaneOut = false - self.gifPane.removeFromSupernode() - } - self.gifPane.layer.removeAllAnimations() - self.stickerPane.layer.removeAllAnimations() - if self.animatingStickerPaneOut { - self.animatingStickerPaneOut = false - self.stickerPane.removeFromSupernode() - } - case .changed: - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) = self.validLayout { - let translationX = -recognizer.translation(in: self.view).x - var indexTransition = translationX / width - if self.paneArrangement.currentIndex == 0 { - indexTransition = max(0.0, indexTransition) - } else if self.paneArrangement.currentIndex == self.paneArrangement.panes.count - 1 { - indexTransition = min(0.0, indexTransition) - } - self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition) - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) - } - case .ended: - if let (width, _, _, _, _, _, _, _, _, _, _, _, _) = self.validLayout { - var updatedIndex = self.paneArrangement.currentIndex - if abs(self.paneArrangement.indexTransition * width) > 30.0 { - if self.paneArrangement.indexTransition < 0.0 { - updatedIndex = max(0, self.paneArrangement.currentIndex - 1) - } else { - updatedIndex = min(self.paneArrangement.panes.count - 1, self.paneArrangement.currentIndex + 1) - } - } - self.paneArrangement = self.paneArrangement.withIndexTransition(0.0) - self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring)) - } - case .cancelled: - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) = self.validLayout { - self.paneArrangement = self.paneArrangement.withIndexTransition(0.0) - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) - } - default: - break - } - } - - private var isExpanded: Bool { - var isExpanded: Bool = false - if let validLayout = self.validLayout, case let .media(_, maybeExpanded, _) = validLayout.8.inputMode, maybeExpanded != nil { - isExpanded = true - } - return isExpanded - } - - private func updatePaneDidScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState, transition: ContainedViewLayoutTransition) { - if self.isExpanded { - pane.collectionListPanelOffset = 0.0 - } else { - var computedAbsoluteOffset: CGFloat - if let absoluteOffset = state.absoluteOffset, absoluteOffset >= 0.0 { - computedAbsoluteOffset = 0.0 - } else { - computedAbsoluteOffset = pane.collectionListPanelOffset + state.relativeChange - } - computedAbsoluteOffset = max(-41.0, min(computedAbsoluteOffset, 0.0)) - pane.collectionListPanelOffset = computedAbsoluteOffset - if transition.isAnimated { - if pane.collectionListPanelOffset < -41.0 / 2.0 { - pane.collectionListPanelOffset = -41.0 - } else { - pane.collectionListPanelOffset = 0.0 - } - } - } - - var collectionListPanelOffset = self.currentCollectionListPanelOffset() - if self.panelIsFocused { - collectionListPanelOffset = 0.0 - } - - let listPanelOffset = collectionListPanelOffset * 2.0 - - self.updateAppearanceTransition(transition: transition) - transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: listPanelOffset), size: self.collectionListPanel.bounds.size)) - transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - listPanelOffset) / 2.0)) - transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - listPanelOffset) / 2.0)) - - self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition) - } - - private func updatePaneClippingContainer(size: CGSize, offset: CGFloat, transition: ContainedViewLayoutTransition) { - var offset = offset - if self.panelIsFocused { - offset = 0.0 - } - transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: self.collectionListSeparator.bounds.size)) - transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: size)) - transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0)) - } - - private func fixPaneScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState) { - if let absoluteOffset = state.absoluteOffset, absoluteOffset >= 0.0 { - pane.collectionListPanelOffset = 0.0 - } else { - if pane.collectionListPanelOffset < -41.0 / 2.0 { - pane.collectionListPanelOffset = -41.0 - } else { - pane.collectionListPanelOffset = 0.0 - } - } - - var collectionListPanelOffset = self.currentCollectionListPanelOffset() - if self.panelIsFocused { - collectionListPanelOffset = 0.0 - } - - let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .spring) - self.updateAppearanceTransition(transition: transition) - transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size)) - transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) - transition.updatePosition(node: self.gifListView, position: CGPoint(x: self.gifListView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) - - self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition) - } - - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - if self.panelIsFocused { - if point.y > -41.0 { - return true - } - } - return super.point(inside: point, with: event) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.panelIsFocused { - if point.y > -41.0 && point.y < 38.0 { - let convertedPoint = CGPoint(x: max(0.0, point.y), y: point.x) - if let result = self.listView.hitTest(convertedPoint, with: event) { - return result - } - if let result = self.gifListView.hitTest(convertedPoint, with: event) { - return result - } - } - } - if let searchContainerNode = self.searchContainerNode { - if let result = searchContainerNode.hitTest(point.offsetBy(dx: -searchContainerNode.frame.minX, dy: -searchContainerNode.frame.minY), with: event) { - return result - } - } - let result = super.hitTest(point, with: event) - return result - } - - static func setupPanelIconInsets(item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> UIEdgeInsets { - var insets = UIEdgeInsets() - if previousItem != nil { - insets.top += 3.0 - } - if nextItem != nil { - insets.bottom += 3.0 - } - return insets - } - - private func dismissPeerSpecificPackSetup() { - guard let peerId = self.peerId else { - return - } - self.dismissedPeerSpecificStickerPack.set(.single(true)) - let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: nil, { current in - return current.withUpdatedMessageActionsState({ value in - var value = value - value.closedPeerSpecificPackSetup = true - return value - }) - }).start() - } -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { - let controller: ViewController - weak var sourceNode: ASDisplayNode? - let sourceRect: CGRect - - let navigationController: NavigationController? = nil - - let passthroughTouches: Bool = false - - init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect) { - self.controller = controller - self.sourceNode = sourceNode - self.sourceRect = sourceRect - } - - func transitionInfo() -> ContextControllerTakeControllerInfo? { - let sourceNode = self.sourceNode - let sourceRect = self.sourceRect - return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in - if let sourceNode = sourceNode { - return (sourceNode.view, sourceRect) - } else { - return nil - } - }) - } - - func animatedIn() { - if let controller = self.controller as? GalleryController { - controller.viewDidAppear(false) - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputPane.swift deleted file mode 100644 index 183d06f694..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputPane.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramPresentationData -import ChatPresentationInterfaceState - -struct ChatMediaInputPaneScrollState { - let absoluteOffset: CGFloat? - let relativeChange: CGFloat -} - -class ChatMediaInputPane: ASDisplayNode { - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var collectionListPanelOffset: CGFloat = 0.0 - var isEmpty: Bool { - return false - } - - func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { - } - - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift deleted file mode 100644 index 56a8ba81de..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift +++ /dev/null @@ -1,337 +0,0 @@ -import Postbox -import UIKit -import TelegramCore -import SwiftSignalKit -import Display -import TelegramPresentationData -import MergeLists -import AccountContext -import ChatPresentationInterfaceState - -enum ChatMediaInputPanelAuxiliaryNamespace: Int32 { - case savedStickers = 2 - case recentGifs = 3 - case recentStickers = 4 - case peerSpecific = 5 - case trending = 6 - case premium = 7 - case settings = 8 -} - -enum ChatMediaInputPanelEntryStableId: Hashable { - case recentGifs - case savedStickers - case recentPacks - case stickerPack(Int64) - case peerSpecific - case trending - case settings - case stickersMode - case savedGifs - case trendingGifs - case premium - case gifEmotion(String) -} - -enum ChatMediaInputPanelEntry: Comparable, Identifiable { - case recentGifs(PresentationTheme, PresentationStrings, Bool) - case savedStickers(PresentationTheme, PresentationStrings, Bool) - case recentPacks(PresentationTheme, PresentationStrings, Bool) - case trending(Bool, PresentationTheme, PresentationStrings, Bool) - case settings(PresentationTheme, PresentationStrings, Bool) - case peerSpecific(theme: PresentationTheme, peer: Peer, expanded: Bool) - case premium(PresentationTheme, PresentationStrings, Bool) - case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, theme: PresentationTheme, expanded: Bool, reorderable: Bool) - - case stickersMode(PresentationTheme, PresentationStrings, Bool) - case savedGifs(PresentationTheme, PresentationStrings, Bool) - case trendingGifs(PresentationTheme, PresentationStrings, Bool) - case gifEmotion(Int, PresentationTheme, PresentationStrings, String, TelegramMediaFile?, Bool) - - var stableId: ChatMediaInputPanelEntryStableId { - switch self { - case .recentGifs: - return .recentGifs - case .savedStickers: - return .savedStickers - case .recentPacks: - return .recentPacks - case .trending: - return .trending - case .settings: - return .settings - case .peerSpecific: - return .peerSpecific - case .premium: - return .premium - case let .stickerPack(_, info, _, _, _, _): - return .stickerPack(info.id.id) - case .stickersMode: - return .stickersMode - case .savedGifs: - return .savedGifs - case .trendingGifs: - return .trendingGifs - case let .gifEmotion(_, _, _, emoji, _, _): - return .gifEmotion(emoji) - } - } - - static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool { - switch lhs { - case let .recentGifs(lhsTheme, lhsStrings, lhsExpanded): - if case let .recentGifs(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .savedStickers(lhsTheme, lhsStrings, lhsExpanded): - if case let .savedStickers(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .recentPacks(lhsTheme, lhsStrings, lhsExpanded): - if case let .recentPacks(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .trending(lhsElevated, lhsTheme, lhsStrings, lhsExpanded): - if case let .trending(rhsElevated, rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsElevated == rhsElevated, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .settings(lhsTheme, lhsStrings, lhsExpanded): - if case let .settings(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .peerSpecific(lhsTheme, lhsPeer, lhsExpanded): - if case let .peerSpecific(rhsTheme, rhsPeer, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .premium(lhsTheme, lhsStrings, lhsExpanded): - if case let .premium(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .stickerPack(index, info, topItem, lhsTheme, lhsExpanded, lhsReorderable): - if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsTheme, rhsExpanded, rhsReorderable) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsTheme === rhsTheme, lhsExpanded == rhsExpanded, lhsReorderable == rhsReorderable { - return true - } else { - return false - } - case let .stickersMode(lhsTheme, lhsStrings, lhsExpanded): - if case let .stickersMode(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .savedGifs(lhsTheme, lhsStrings, lhsExpanded): - if case let .savedGifs(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .trendingGifs(lhsTheme, lhsStrings, lhsExpanded): - if case let .trendingGifs(rhsTheme, rhsStrings, rhsExpanded) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpanded == rhsExpanded { - return true - } else { - return false - } - case let .gifEmotion(lhsIndex, lhsTheme, lhsStrings, lhsEmoji, lhsFile, lhsExpanded): - if case let .gifEmotion(rhsIndex, rhsTheme, rhsStrings, rhsEmoji, rhsFile, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, 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 - } - } - } - - static func <(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool { - switch lhs { - case .recentGifs: - switch rhs { - case .recentGifs: - return false - default: - return true - } - case .savedStickers: - switch rhs { - case .recentGifs, savedStickers: - return false - case let .trending(elevated, _, _, _) where elevated: - return false - default: - return true - } - case .recentPacks: - switch rhs { - case .recentGifs, .savedStickers, recentPacks: - return false - case let .trending(elevated, _, _, _) where elevated: - return false - default: - return true - } - case .peerSpecific: - switch rhs { - case .recentGifs, .savedStickers, recentPacks, .peerSpecific: - return false - case let .trending(elevated, _, _, _) where elevated: - return false - default: - return true - } - case .premium: - switch rhs { - case .recentGifs, .savedStickers, recentPacks, .peerSpecific, .premium: - return false - case let .trending(elevated, _, _, _) where elevated: - return false - default: - return true - } - case let .stickerPack(lhsIndex, lhsInfo, _, _, _, _): - switch rhs { - case .recentGifs, .savedStickers, .recentPacks, .peerSpecific: - return false - case let .trending(elevated, _, _, _): - if elevated { - return false - } else { - return true - } - case .settings: - return true - case let .stickerPack(rhsIndex, rhsInfo, _, _, _, _): - if lhsIndex == rhsIndex { - return lhsInfo.id.id < rhsInfo.id.id - } else { - return lhsIndex <= rhsIndex - } - default: - return true - } - case let .trending(elevated, _, _, _): - if elevated { - switch rhs { - case .recentGifs, .trending: - return false - default: - return true - } - } else { - if case .settings = rhs { - return true - } else { - return false - } - } - case .stickersMode: - return false - case .savedGifs: - switch rhs { - case .savedGifs: - return false - default: - return true - } - case .trendingGifs: - switch rhs { - case .stickersMode, .savedGifs, .trendingGifs: - return false - default: - return true - } - case let .gifEmotion(lhsIndex, _, _, _, _, _): - switch rhs { - case .stickersMode, .savedGifs, .trendingGifs: - return false - case let .gifEmotion(rhsIndex, _, _, _, _, _): - return lhsIndex < rhsIndex - default: - return true - } - case .settings: - if case .settings = rhs { - return false - } else { - return true - } - } - } - - func item(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem { - switch self { - case let .recentGifs(theme, strings, expanded): - return ChatMediaInputRecentGifsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, strings: strings, expanded: expanded, selected: { - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0) - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .savedStickers(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, strings: strings, expanded: expanded, selected: { - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0) - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .recentPacks(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, strings: strings, expanded: expanded, selected: { - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .trending(elevated, theme, strings, expanded): - return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, strings: strings, expanded: expanded, selected: { - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0) - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .settings(theme, strings, expanded): - return ChatMediaInputSettingsItem(inputNodeInteraction: inputNodeInteraction, theme: theme, strings: strings, expanded: expanded, selected: { - inputNodeInteraction.openSettings() - }) - case let .peerSpecific(theme, peer, expanded): - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0) - return ChatMediaInputPeerSpecificItem(context: context, inputNodeInteraction: inputNodeInteraction, collectionId: collectionId, peer: peer, theme: theme, expanded: expanded, selected: { - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .premium(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .premium, theme: theme, strings: strings, expanded: expanded, selected: { - let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0) - inputNodeInteraction.navigateToCollectionId(collectionId) - }) - case let .stickerPack(index, info, topItem, theme, expanded, reorderable): - return ChatMediaInputStickerPackItem(account: context.account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, index: index, theme: theme, expanded: expanded, reorderable: reorderable, selected: { - inputNodeInteraction.navigateToCollectionId(info.id) - }) - case let .stickersMode(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, strings: strings, expanded: expanded, selected: { - inputNodeInteraction.navigateBackToStickers() - }) - case let .savedGifs(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, strings: strings, expanded: expanded, selected: { - inputNodeInteraction.setGifMode(.recent) - }) - case let .trendingGifs(theme, strings, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, strings: strings, expanded: expanded, selected: { - inputNodeInteraction.setGifMode(.trending) - }) - case let .gifEmotion(_, theme, strings, emoji, file, expanded): - return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji, file), theme: theme, strings: strings, expanded: expanded, selected: { - inputNodeInteraction.setGifMode(.emojiSearch(emoji)) - }) - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift deleted file mode 100644 index 1177f8871a..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift +++ /dev/null @@ -1,180 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import AvatarNode -import AccountContext -import ChatPresentationInterfaceState - -final class ChatMediaInputPeerSpecificItem: ListViewItem { - let context: AccountContext - let inputNodeInteraction: ChatMediaInputNodeInteraction - let collectionId: ItemCollectionId - let peer: Peer - let expanded: Bool - let selectedItem: () -> Void - let theme: PresentationTheme - - var selectable: Bool { - return true - } - - init(context: AccountContext, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, peer: Peer, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { - self.context = context - self.inputNodeInteraction = inputNodeInteraction - self.collectionId = collectionId - self.peer = peer - self.selectedItem = selected - self.expanded = expanded - self.theme = theme - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputPeerSpecificItemNode() - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in - node.updateItem(context: self.context, peer: self.peer, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded) - node.updateAppearanceTransition(transition: .immediate) - }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputPeerSpecificItemNode)?.updateItem(context: self.context, peer: self.peer, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let avatarFont = avatarPlaceholderFont(size: 19.0) -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageSize = CGSize(width: 45.0, height: 45.0) -private let boundingImageScale: CGFloat = 0.625 -private let highlightSize = CGSize(width: 56.0, height: 56.0) -private let verticalOffset: CGFloat = 3.0 - -final class ChatMediaInputPeerSpecificItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let avatarNode: AvatarNode - private let highlightNode: ASImageNode - private let titleNode: ImmediateTextNode - - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var currentCollectionId: ItemCollectionId? - private var currentExpanded = false - private var theme: PresentationTheme? - - private let stickerFetchedDisposable = MetaDisposable() - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.scalingNode = ASDisplayNode() - - self.highlightNode = ASImageNode() - self.highlightNode.isLayerBacked = true - self.highlightNode.isHidden = true - - self.avatarNode = AvatarNode(font: avatarFont) - self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.highlightNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.avatarNode) - } - - deinit { - self.stickerFetchedDisposable.dispose() - } - - func updateItem(context: AccountContext, peer: Peer, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool) { - self.currentCollectionId = collectionId - - if self.theme !== theme { - self.theme = theme - - self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) - - self.titleNode.attributedText = NSAttributedString(string: EnginePeer(peer).compactDisplayTitle, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) - } - - self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6) - - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 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.avatarNode.position.y - titleFrame.size.height), size: titleFrame.size) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - self.currentExpanded = expanded - - self.avatarNode.bounds = CGRect(origin: CGPoint(), size: imageSize) - self.avatarNode.position = CGPoint(x: expandedBoundingSize.height / 2.0, y: expandedBoundingSize.width / 2.0) - self.avatarNode.frame = self.avatarNode.frame - expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.avatarNode.position.x - highlightSize.width / 2.0, y: self.avatarNode.position.y - highlightSize.height / 2.0), size: highlightSize)) - - self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer)) - } - - func updateIsHighlighted() { - assert(Queue.mainQueue().isCurrent()) - if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction { - self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId - } - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - assert(Queue.mainQueue().isCurrent()) - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift deleted file mode 100644 index 0e1ececabd..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift +++ /dev/null @@ -1,159 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import ChatPresentationInterfaceState - -final class ChatMediaInputRecentGifsItem: ListViewItem { - let inputNodeInteraction: ChatMediaInputNodeInteraction - let selectedItem: () -> Void - let expanded: Bool - let theme: PresentationTheme - let strings: PresentationStrings - - var selectable: Bool { - return true - } - - init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool, selected: @escaping () -> Void) { - self.inputNodeInteraction = inputNodeInteraction - self.selectedItem = selected - self.theme = theme - self.strings = strings - self.expanded = expanded - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputRecentGifsItemNode() - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - node.updateIsHighlighted() - node.updateAppearanceTransition(transition: .immediate) - Queue.mainQueue().async { - node.updateTheme(theme: self.theme, strings: self.strings, expanded: self.expanded) - completion(node, { - return (nil, { _ in }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme, strings: self.strings, expanded: self.expanded) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageScale: CGFloat = 0.625 -private let highlightSize = CGSize(width: 56.0, height: 56.0) -private let verticalOffset: CGFloat = 3.0 + UIScreenPixel - -final class ChatMediaInputRecentGifsItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let imageNode: ASImageNode - private let highlightNode: ASImageNode - private let titleNode: ImmediateTextNode - - private var currentExpanded = false - - var currentCollectionId: ItemCollectionId? - var inputNodeInteraction: ChatMediaInputNodeInteraction? - - var theme: PresentationTheme? - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.scalingNode = ASDisplayNode() - - self.highlightNode = ASImageNode() - self.highlightNode.isLayerBacked = true - self.highlightNode.isHidden = true - - self.imageNode = ASImageNode() - self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .scaleAspectFit - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.highlightNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.imageNode) - - self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0) - } - - deinit { - } - - func updateTheme(theme: PresentationTheme, strings: PresentationStrings, expanded: Bool) { - if self.theme !== theme { - self.theme = theme - - self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelRecentGifsIconImage(theme) - - self.titleNode.attributedText = NSAttributedString(string: strings.Stickers_Gifs, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) - } - - 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.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 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) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - self.currentExpanded = expanded - - 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)) - } - - func updateIsHighlighted() { - if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction { - self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId - } - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift deleted file mode 100644 index f8f1ecb181..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift +++ /dev/null @@ -1,143 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import ChatPresentationInterfaceState - -final class ChatMediaInputSettingsItem: ListViewItem { - let inputNodeInteraction: ChatMediaInputNodeInteraction - let selectedItem: () -> Void - let expanded: Bool - let theme: PresentationTheme - let strings: PresentationStrings - - var selectable: Bool { - return true - } - - init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool, selected: @escaping () -> Void) { - self.inputNodeInteraction = inputNodeInteraction - self.selectedItem = selected - self.theme = theme - self.strings = strings - self.expanded = expanded - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputSettingsItemNode() - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - node.updateAppearanceTransition(transition: .immediate) - Queue.mainQueue().async { - node.updateTheme(theme: self.theme, strings: self.strings, expanded: self.expanded) - completion(node, { - return (nil, { _ in }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme, strings: self.strings, expanded: self.expanded) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageScale: CGFloat = 0.625 -private let verticalOffset: CGFloat = 3.0 + UIScreenPixel - -final class ChatMediaInputSettingsItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let buttonNode: HighlightableButtonNode - private let imageNode: ASImageNode - private let titleNode: ImmediateTextNode - - private var currentExpanded = false - - var currentCollectionId: ItemCollectionId? - var inputNodeInteraction: ChatMediaInputNodeInteraction? - - var theme: PresentationTheme? - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.scalingNode = ASDisplayNode() - - self.buttonNode = HighlightableButtonNode() - - self.imageNode = ASImageNode() - self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .scaleAspectFit - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.buttonNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.imageNode) - } - - func updateTheme(theme: PresentationTheme, strings: PresentationStrings, 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) - - if self.theme !== theme { - self.theme = theme - - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelSettingsIconImage(theme) - - self.titleNode.attributedText = NSAttributedString(string: strings.Stickers_Settings, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) - } - - self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - self.buttonNode.frame = self.scalingNode.bounds - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 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) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - self.currentExpanded = expanded - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } -} - diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index 253d3006ea..b845f0097c 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -139,19 +139,10 @@ final class ChatMediaInputStickerGridItem: GridItem { self.large = large self.isLocked = isLocked self.selected = selected - if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue { - self.section = nil - } else { - let accessory: ChatMediaInputStickerGridSectionAccessory - if hasAccessory && stickerPackInfo?.id.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, let canManage = canManagePeerSpecificPack, canManage { - accessory = .setup - } else if hasAccessory && stickerPackInfo?.id.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { - accessory = .clear - } else { - accessory = .none - } - self.section = ChatMediaInputStickerGridSection(collectionId: collectionId, collectionInfo: stickerPackInfo, accessory: accessory, theme: theme, interaction: inputNodeInteraction) - } + + let accessory: ChatMediaInputStickerGridSectionAccessory + accessory = .none + self.section = ChatMediaInputStickerGridSection(collectionId: collectionId, collectionInfo: stickerPackInfo, accessory: accessory, theme: theme, interaction: inputNodeInteraction) } func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift deleted file mode 100644 index 3a9c2fcea4..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift +++ /dev/null @@ -1,410 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import StickerResources -import ItemListStickerPackItem -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import ShimmerEffect -import ChatPresentationInterfaceState - -final class ChatMediaInputStickerPackItem: ListViewItem { - let account: Account - let inputNodeInteraction: ChatMediaInputNodeInteraction - let collectionId: ItemCollectionId - let collectionInfo: StickerPackCollectionInfo - let stickerPackItem: StickerPackItem? - let selectedItem: () -> Void - let index: Int - let theme: PresentationTheme - let expanded: Bool - let reorderable: Bool - - var selectable: Bool { - return true - } - - init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, index: Int, theme: PresentationTheme, expanded: Bool, reorderable: Bool, selected: @escaping () -> Void) { - self.account = account - self.inputNodeInteraction = inputNodeInteraction - self.collectionId = collectionId - self.collectionInfo = collectionInfo - self.stickerPackItem = stickerPackItem - self.selectedItem = selected - self.index = index - self.theme = theme - self.expanded = expanded - self.reorderable = reorderable - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputStickerPackItemNode() - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in - node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded, reorderable: self.reorderable) - node.updateAppearanceTransition(transition: .immediate) - }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputStickerPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme, expanded: self.expanded, reorderable: self.reorderable) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageSize = CGSize(width: 45.0, height: 45.0) -private let boundingImageScale: CGFloat = 0.625 -private let highlightSize = CGSize(width: 56.0, height: 56.0) -private let verticalOffset: CGFloat = -3.0 - -final class ChatMediaInputStickerPackItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let imageNode: TransformImageNode - private var animatedStickerNode: AnimatedStickerNode? - private var placeholderNode: StickerShimmerEffectNode? - private let highlightNode: ASImageNode - private let titleNode: ImmediateTextNode - - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var currentCollectionId: ItemCollectionId? - private var account: Account? - private var currentThumbnailItem: StickerPackThumbnailItem? - private var currentExpanded = false - private var theme: PresentationTheme? - private var reorderable = false - - private let stickerFetchedDisposable = MetaDisposable() - - 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 - } - } - } - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.scalingNode = ASDisplayNode() - - self.highlightNode = ASImageNode() - self.highlightNode.isLayerBacked = true - self.highlightNode.isHidden = true - - self.imageNode = TransformImageNode() - self.imageNode.isLayerBacked = !smartInvertColorsEnabled() - - self.placeholderNode = StickerShimmerEffectNode() - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.highlightNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.imageNode) - if let placeholderNode = self.placeholderNode { - self.scalingNode.addSubnode(placeholderNode) - } - - var firstTime = true - self.imageNode.imageUpdated = { [weak self] image in - guard let strongSelf = self else { - return - } - if image != nil { - strongSelf.removePlaceholder(animated: !firstTime) - if firstTime { - strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - firstTime = false - } - } - - deinit { - self.stickerFetchedDisposable.dispose() - } - - private func removePlaceholder(animated: Bool) { - if let placeholderNode = self.placeholderNode { - self.placeholderNode = nil - if !animated { - placeholderNode.removeFromSupernode() - } else { - placeholderNode.alpha = 0.0 - placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in - placeholderNode?.removeFromSupernode() - }) - } - } - } - - func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool, reorderable: Bool) { - self.currentCollectionId = collectionId - self.account = account - self.reorderable = reorderable - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - - self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) - } - - var thumbnailItem: StickerPackThumbnailItem? - var resourceReference: MediaResourceReference? - if let thumbnail = info.thumbnail { - if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { - thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, info.flags.contains(.isVideo)) - } else { - thumbnailItem = .still(thumbnail) - } - resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource) - } else if let item = item { - if item.file.isAnimatedSticker || item.file.isVideoSticker { - thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100), item.file.isVideoSticker) - resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) - } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { - thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) - } - } - - if themeUpdated || self.titleNode.attributedText?.string != info.title { - self.titleNode.attributedText = NSAttributedString(string: info.title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) - } - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - var imageSize = boundingImageSize - - if self.currentThumbnailItem != thumbnailItem { - self.currentThumbnailItem = thumbnailItem - if let thumbnailItem = thumbnailItem { - switch thumbnailItem { - case let .still(representation): - imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize) - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 6.0), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) - imageApply() - self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true)) - case let .animated(resource, dimensions, isVideo): - imageSize = dimensions.cgSize.aspectFitted(boundingImageSize) - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) - imageApply() - self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true)) - - let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false - - let animatedStickerNode: AnimatedStickerNode - if let current = self.animatedStickerNode { - animatedStickerNode = current - } else { - animatedStickerNode = DefaultAnimatedStickerNodeImpl() - animatedStickerNode.started = { [weak self] in - self?.imageNode.isHidden = true - self?.removePlaceholder(animated: false) - } - 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: resource, isVideo: isVideo), width: 128, height: 128, playbackMode: .loop, mode: .cached) - } - animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers - } - if let resourceReference = resourceReference { - self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: resourceReference).start()) - } - } - - self.updateIsHighlighted() - } - - if let placeholderNode = self.placeholderNode { - var imageSize = PixelDimensions(width: 512, height: 512) - var immediateThumbnailData: Data? - if let data = info.immediateThumbnailData { - if info.flags.contains(.isVideo) { - imageSize = PixelDimensions(width: 100, height: 100) - } - immediateThumbnailData = data - } else if let data = item?.file.immediateThumbnailData { - immediateThumbnailData = data - } - - placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: immediateThumbnailData, size: boundingImageSize, enableEffect: true, imageSize: imageSize.cgSize) - } - - self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 - 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) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - self.currentExpanded = expanded - - self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize) - self.imageNode.position = CGPoint(x: expandedBoundingSize.height / 2.0, y: expandedBoundingSize.width / 2.0) - if let animatedStickerNode = self.animatedStickerNode { - animatedStickerNode.frame = self.imageNode.frame - animatedStickerNode.updateLayout(size: self.imageNode.frame.size) - } - if let placeholderNode = self.placeholderNode { - placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize) - placeholderNode.position = self.imageNode.position - } - 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)) - } - - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { - if let placeholderNode = self.placeholderNode { - placeholderNode.updateAbsoluteRect(rect, within: containerSize) - } - } - - func updateIsHighlighted() { - assert(Queue.mainQueue().isCurrent()) - if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction { - self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId - } - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - assert(Queue.mainQueue().isCurrent()) - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - 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 { - guard self.reorderable else { - return false - } - if self.bounds.inset(by: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -28.0)).contains(point) { - return true - } - return false - } - - override func snapshotForReordering() -> UIView? { - if let account = self.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(radius: 6.0), 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, dimensions, isVideo): - imageSize = dimensions.cgSize.aspectFitted(boundingImageSize) - - let animatedStickerNode = DefaultAnimatedStickerNodeImpl() - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: 128, height: 128, mode: .cached) - 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 - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift deleted file mode 100644 index 8c10f5497e..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift +++ /dev/null @@ -1,202 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramPresentationData -import ChatInputNode -import FeaturedStickersScreen - -private func fixGridScrolling(_ gridNode: GridNode) { - var searchItemNode: GridItemNode? - var nextItemNode: GridItemNode? - - gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { - searchItemNode = itemNode - } else if searchItemNode != nil && nextItemNode == nil, let itemNode = itemNode as? GridItemNode { - nextItemNode = itemNode - } - } - - if let searchItemNode = searchItemNode { - let contentInset = gridNode.scrollView.contentInset.top - let itemFrame = gridNode.convert(searchItemNode.frame, to: gridNode.supernode) - if itemFrame.contains(CGPoint(x: 0.0, y: contentInset)) { - let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) - - var scrollIndex: Int? - if itemFrame.minY + itemFrame.height * 0.6 < contentInset { - for i in 0 ..< gridNode.items.count { - if let _ = gridNode.items[i] as? StickerPaneTrendingListGridItem { - scrollIndex = i - break - } else if let _ = gridNode.items[i] as? ChatMediaInputStickerGridItem { - scrollIndex = i - break - } - } - } else { - for i in 0 ..< gridNode.items.count { - if let _ = gridNode.items[i] as? PaneSearchBarPlaceholderItem { - scrollIndex = i - break - } - } - } - - if let scrollIndex = scrollIndex { - gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: scrollIndex, position: .top(0.0), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in }) - } - } - } -} - -final class ChatMediaInputStickerPaneOpaqueState { - let hasLower: Bool - - init(hasLower: Bool) { - self.hasLower = hasLower - } -} - -final class ChatMediaInputStickerPane: ChatMediaInputPane { - private var isExpanded: Bool? - private var isPaneVisible = false - let gridNode: GridNode - private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void - private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void - private var didScrollPreviousOffset: CGFloat? - private var didScrollPreviousState: ChatMediaInputPaneScrollState? - - var beganScrolling: (() -> Void)? - var endedScrolling: (() -> Void)? - - init(theme: PresentationTheme, strings: PresentationStrings, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void) { - self.gridNode = GridNode() - self.paneDidScroll = paneDidScroll - self.fixPaneScroll = fixPaneScroll - - super.init() - - self.addSubnode(self.gridNode) - self.gridNode.presentationLayoutUpdated = { [weak self] layout, transition in - if let strongSelf = self, let opaqueState = strongSelf.gridNode.opaqueState as? ChatMediaInputStickerPaneOpaqueState { - var offset: CGFloat - if opaqueState.hasLower { - offset = -(layout.contentOffset.y + 41.0) - } else { - offset = -(layout.contentOffset.y + 41.0) - offset = min(0.0, offset + 56.0) - } - var relativeChange: CGFloat = 0.0 - if let didScrollPreviousOffset = strongSelf.didScrollPreviousOffset { - relativeChange = offset - didScrollPreviousOffset - } - strongSelf.didScrollPreviousOffset = offset - let state = ChatMediaInputPaneScrollState(absoluteOffset: offset, relativeChange: relativeChange) - strongSelf.didScrollPreviousState = state - if !transition.isAnimated { - strongSelf.paneDidScroll(strongSelf, state, transition) - } - } - } - self.gridNode.scrollingInitiated = { [weak self] in - self?.beganScrolling?() - } - self.gridNode.scrollingCompleted = { [weak self] in - if let strongSelf = self { - if let didScrollPreviousState = strongSelf.didScrollPreviousState { - strongSelf.fixPaneScroll(strongSelf, didScrollPreviousState) - } - fixGridScrolling(strongSelf.gridNode) - strongSelf.endedScrolling?() - } - } - self.gridNode.setupNode = { [weak self] itemNode in - guard let strongSelf = self else { - return - } - if let itemNode = itemNode as? ChatMediaInputStickerGridItemNode { - itemNode.updateIsPanelVisible(strongSelf.isPaneVisible) - } else if let itemNode = itemNode as? StickerPaneTrendingListGridItemNode { - itemNode.updateIsPanelVisible(strongSelf.isPaneVisible) - } - } - self.gridNode.scrollView.alwaysBounceVertical = true - } - - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { - var changedIsExpanded = false - if let previousIsExpanded = self.isExpanded { - if previousIsExpanded != isExpanded { - changedIsExpanded = true - } - } - self.isExpanded = isExpanded - - let maxItemSize: CGSize - if case .tablet = deviceMetrics.type, size.width > 480.0 { - maxItemSize = CGSize(width: 90.0, height: 96.0) - } else { - maxItemSize = CGSize(width: 75.0, height: 80.0) - } - - let sideInset: CGFloat = 2.0 - var itemSide: CGFloat = floor((size.width - sideInset * 2.0) / 5.0) - itemSide = min(itemSide, maxItemSize.width) - let itemSize = CGSize(width: itemSide, height: max(itemSide, maxItemSize.height)) - - var scrollToItem: GridNodeScrollToItem? - if changedIsExpanded { - if isExpanded { - var scrollIndex: Int? - for i in 0 ..< self.gridNode.items.count { - if let _ = self.gridNode.items[i] as? PaneSearchBarPlaceholderItem { - scrollIndex = i - break - } - } - if let scrollIndex = scrollIndex { - scrollToItem = GridNodeScrollToItem(index: scrollIndex, position: .top(0.0), transition: transition, directionHint: .down, adjustForSection: true, adjustForTopInset: true) - } - } else { - var scrollIndex: Int? - for i in 0 ..< self.gridNode.items.count { - if let _ = self.gridNode.items[i] as? ChatMediaInputStickerGridItem { - scrollIndex = i - break - } - } - if let scrollIndex = scrollIndex { - scrollToItem = GridNodeScrollToItem(index: scrollIndex, position: .top(0.0), transition: transition, directionHint: .down, adjustForSection: true, adjustForTopInset: true) - } - } - } - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), preloadSize: isVisible ? 300.0 : 0.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - - if self.isPaneVisible != isVisible { - self.isPaneVisible = isVisible - self.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputStickerGridItemNode { - itemNode.updateIsPanelVisible(isVisible) - } else if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { - itemNode.updateCanPlayMedia() - } else if let itemNode = itemNode as? StickerPaneTrendingListGridItemNode { - itemNode.updateIsPanelVisible(isVisible) - } - } - } - } - - func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { - if let itemNode = self.gridNode.itemNodeAtPoint(self.view.convert(point, to: self.gridNode.view)) as? ChatMediaInputStickerGridItemNode, let stickerPackItem = itemNode.stickerPackItem { - return (itemNode, stickerPackItem) - } - return nil - } -} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift deleted file mode 100644 index 03824271eb..0000000000 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift +++ /dev/null @@ -1,186 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import ChatPresentationInterfaceState - -final class ChatMediaInputTrendingItem: ListViewItem { - let inputNodeInteraction: ChatMediaInputNodeInteraction - let selectedItem: () -> Void - let elevated: Bool - let expanded: Bool - let theme: PresentationTheme - let strings: PresentationStrings - - var selectable: Bool { - return true - } - - init(inputNodeInteraction: ChatMediaInputNodeInteraction, elevated: Bool, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool, selected: @escaping () -> Void) { - self.inputNodeInteraction = inputNodeInteraction - self.elevated = elevated - self.selectedItem = selected - self.expanded = expanded - self.theme = theme - self.strings = strings - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatMediaInputTrendingItemNode() - node.contentSize = self.expanded ? expandedBoundingSize : boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - node.updateIsHighlighted() - node.updateAppearanceTransition(transition: .immediate) - Queue.mainQueue().async { - node.updateTheme(elevated: self.elevated, theme: self.theme, strings: self.strings, expanded: self.expanded) - completion(node, { - return (nil, { _ in }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? ChatMediaInputTrendingItemNode)?.updateTheme(elevated: self.elevated, theme: self.theme, strings: self.strings, expanded: self.expanded) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private let boundingSize = CGSize(width: 72.0, height: 41.0) -private let expandedBoundingSize = CGSize(width: 72.0, height: 72.0) -private let boundingImageScale: CGFloat = 0.625 -private let highlightSize = CGSize(width: 56.0, height: 56.0) -private let verticalOffset: CGFloat = 3.0 + UIScreenPixel - -final class ChatMediaInputTrendingItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let scalingNode: ASDisplayNode - private let imageNode: ASImageNode - private let highlightNode: ASImageNode - private let titleNode: ImmediateTextNode - - private var currentExpanded = false - - var currentCollectionId: ItemCollectionId? - var inputNodeInteraction: ChatMediaInputNodeInteraction? - - var elevated: Bool = false - var theme: PresentationTheme? - - let badgeBackground: ASImageNode - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.scalingNode = ASDisplayNode() - - self.highlightNode = ASImageNode() - self.highlightNode.isLayerBacked = true - self.highlightNode.isHidden = true - - self.imageNode = ASImageNode() - self.imageNode.isLayerBacked = true - self.imageNode.contentMode = .scaleAspectFit - - self.badgeBackground = ASImageNode() - self.badgeBackground.displaysAsynchronously = false - self.badgeBackground.displayWithoutProcessing = true - self.badgeBackground.isHidden = true - - self.titleNode = ImmediateTextNode() - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.scalingNode) - - self.scalingNode.addSubnode(self.highlightNode) - self.scalingNode.addSubnode(self.titleNode) - self.scalingNode.addSubnode(self.imageNode) - self.scalingNode.addSubnode(self.badgeBackground) - - self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0) - } - - func updateTheme(elevated: Bool, theme: PresentationTheme, strings: PresentationStrings, expanded: Bool) { - let imageSize = CGSize(width: 26.0 * 1.85, height: 26.0 * 1.85) - let imageFrame = 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 = imageFrame - - if self.theme !== theme { - self.theme = theme - - self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) - self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme) - self.badgeBackground.image = generateFilledCircleImage(diameter: 10.0, color: theme.chat.inputPanel.mediaRecordingDotColor) - - if let image = self.badgeBackground.image { - self.badgeBackground.frame = CGRect(origin: CGPoint(x: floor(imageFrame.maxX - image.size.width - 7.0), y: 18.0), size: image.size) - } - - self.titleNode.attributedText = NSAttributedString(string: strings.Stickers_Trending, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) - } - - if self.elevated != elevated { - self.elevated = elevated - } - - self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) - self.scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) - - let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) - let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale - let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate - 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 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) - expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) - expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) - - let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) - - self.currentExpanded = expanded - - 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)) - } - - func updateIsHighlighted() { - if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction { - self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId - } - } - - func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { - if let inputNodeInteraction = self.inputNodeInteraction { - transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) - } - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - } -} - diff --git a/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift b/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift deleted file mode 100644 index 5f6f4cc4b4..0000000000 --- a/submodules/TelegramUI/Sources/StickerPanePeerSpecificSetupGridItem.swift +++ /dev/null @@ -1,191 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData - -final class StickerPanePeerSpecificSetupGridItem: GridItem { - let theme: PresentationTheme - let strings: PresentationStrings - let setup: () -> Void - let dismiss: (() -> Void)? - - let section: GridSection? = nil - let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? - - init(theme: PresentationTheme, strings: PresentationStrings, setup: @escaping () -> Void, dismiss: (() -> Void)?) { - self.theme = theme - self.strings = strings - self.setup = setup - self.dismiss = dismiss - self.fillsRowWithDynamicHeight = { width in - let makeDescriptionLayout = TextNode.asyncLayout(nil) - let params = ListViewItemLayoutParams(width: width, leftInset: 0.0, rightInset: 0.0, availableHeight: 0.0) - let leftInset: CGFloat = 12.0 - let rightInset: CGFloat = 16.0 - let (descriptionLayout, _) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Stickers_GroupStickersHelp, font: statusFont, textColor: .black), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - return 71.0 + descriptionLayout.size.height - } - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = StickerPanePeerSpecificSetupGridItemNode() - node.setup(item: self) - return node - } - - func update(node: GridItemNode) { - guard let node = node as? StickerPanePeerSpecificSetupGridItemNode else { - assertionFailure() - return - } - node.setup(item: self) - } -} - -private let titleFont = Font.medium(12.0) -private let statusFont = Font.regular(14.0) -private let buttonFont = Font.medium(13.0) - -class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { - private let titleNode: TextNode - private let descriptionNode: TextNode - private let installTextNode: TextNode - private let installBackgroundNode: ASImageNode - private let installButtonNode: HighlightTrackingButtonNode - private let dismissButtonNode: HighlightTrackingButtonNode - - private var item: StickerPanePeerSpecificSetupGridItem? - private var appliedItem: StickerPanePeerSpecificSetupGridItem? - - override init() { - self.titleNode = TextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.contentMode = .left - self.titleNode.contentsScale = UIScreen.main.scale - - self.descriptionNode = TextNode() - self.descriptionNode.isUserInteractionEnabled = false - self.descriptionNode.contentMode = .left - self.descriptionNode.contentsScale = UIScreen.main.scale - - self.installTextNode = TextNode() - self.installTextNode.isUserInteractionEnabled = false - self.installTextNode.contentMode = .left - self.installTextNode.contentsScale = UIScreen.main.scale - - self.installBackgroundNode = ASImageNode() - self.installBackgroundNode.isLayerBacked = true - self.installBackgroundNode.displayWithoutProcessing = true - self.installBackgroundNode.displaysAsynchronously = false - - self.installButtonNode = HighlightTrackingButtonNode() - self.dismissButtonNode = HighlightTrackingButtonNode() - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.descriptionNode) - self.addSubnode(self.installBackgroundNode) - self.addSubnode(self.installTextNode) - self.addSubnode(self.installButtonNode) - self.addSubnode(self.dismissButtonNode) - - self.installButtonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.installBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.installBackgroundNode.alpha = 0.4 - strongSelf.installTextNode.layer.removeAnimation(forKey: "opacity") - strongSelf.installTextNode.alpha = 0.4 - } else { - strongSelf.installBackgroundNode.alpha = 1.0 - strongSelf.installBackgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.installTextNode.alpha = 1.0 - strongSelf.installTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - - self.installButtonNode.addTarget(self, action: #selector(self.installPressed), forControlEvents: .touchUpInside) - - self.dismissButtonNode.addTarget(self, action: #selector(self.dismissPressed), forControlEvents: .touchUpInside) - } - - func setup(item: StickerPanePeerSpecificSetupGridItem) { - self.item = item - self.setNeedsLayout() - } - - override func layout() { - super.layout() - guard let item = self.item else { - return - } - - let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.size.height) - - let makeInstallLayout = TextNode.asyncLayout(self.installTextNode) - let makeTitleLayout = TextNode.asyncLayout(self.titleNode) - let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) - - let currentItem = self.appliedItem - self.appliedItem = item - - var updateButtonBackgroundImage: UIImage? - if currentItem?.theme !== item.theme { - updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddPackButtonImage(item.theme) - self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme, color: item.theme.chat.inputMediaPanel.stickersSectionTextColor), for: []) - } - - let leftInset: CGFloat = 12.0 - let rightInset: CGFloat = 16.0 - let topOffset: CGFloat = 9.0 - let textSpacing: CGFloat = 3.0 - let buttonSpacing: CGFloat = 6.0 - - let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickers.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickersHelp, font: statusFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - self.item = item - - let _ = installApply() - let _ = titleApply() - let _ = descriptionApply() - - if let updateButtonBackgroundImage = updateButtonBackgroundImage { - self.installBackgroundNode.image = updateButtonBackgroundImage - } - - let installWidth: CGFloat = installLayout.size.width + 20.0 - let buttonFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing + descriptionLayout.size.height + buttonSpacing), size: CGSize(width: installWidth, height: 32.0)) - self.installBackgroundNode.frame = buttonFrame - self.installTextNode.frame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - installLayout.size.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - installLayout.size.height) / 2.0)), size: installLayout.size) - self.installButtonNode.frame = buttonFrame - - let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset), size: titleLayout.size) - let dismissButtonSize = CGSize(width: 12.0, height: 12.0) - self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width, y: topOffset - 1.0), size: dismissButtonSize) - self.dismissButtonNode.isHidden = item.dismiss == nil - self.titleNode.frame = titleFrame - self.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing), size: descriptionLayout.size) - } - - @objc private func installPressed() { - if let item = self.item { - item.setup() - } - } - - @objc private func dismissPressed() { - if let item = self.item { - item.dismiss?() - } - } -} diff --git a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift deleted file mode 100644 index 15021188dd..0000000000 --- a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift +++ /dev/null @@ -1,554 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import StickerResources -import ItemListStickerPackItem -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import ShimmerEffect -import MergeLists -import ChatPresentationInterfaceState - -private let boundingSize = CGSize(width: 41.0, height: 41.0) -private let boundingImageSize = CGSize(width: 28.0, height: 28.0) - -private struct Transition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] -} - -private enum EntryStableId: Hashable { - case stickerPack(Int64) -} - -private enum Entry: Comparable, Identifiable { - case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, unread: Bool, theme: PresentationTheme) - - var stableId: EntryStableId { - switch self { - case let .stickerPack(_, info, _, _, _): - return .stickerPack(info.id.id) - } - } - - static func ==(lhs: Entry, rhs: Entry) -> Bool { - switch lhs { - case let .stickerPack(index, info, topItem, lhsUnread, lhsTheme): - if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsUnread, rhsTheme) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsUnread == rhsUnread, lhsTheme === rhsTheme { - return true - } else { - return false - } - } - } - - static func <(lhs: Entry, rhs: Entry) -> Bool { - switch lhs { - case let .stickerPack(lhsIndex, lhsInfo, _, _, _): - switch rhs { - case let .stickerPack(rhsIndex, rhsInfo, _, _, _): - if lhsIndex == rhsIndex { - return lhsInfo.id.id < rhsInfo.id.id - } else { - return lhsIndex <= rhsIndex - } - } - } - } - - func item(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, isVisible: @escaping () -> Bool) -> ListViewItem { - switch self { - case let .stickerPack(index, info, topItem, unread, theme): - return FeaturedPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, unread: unread, index: index, theme: theme, selected: { - inputNodeInteraction.openTrending(info.id) - }, isVisible: isVisible) - } - } -} - -private func preparedEntryTransition(account: Account, from fromEntries: [Entry], to toEntries: [Entry], inputNodeInteraction: ChatMediaInputNodeInteraction, isVisible: @escaping () -> Bool) -> Transition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inputNodeInteraction: inputNodeInteraction, isVisible: isVisible), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inputNodeInteraction: inputNodeInteraction, isVisible: isVisible), directionHint: nil) } - - return Transition(deletions: deletions, insertions: insertions, updates: updates) -} - -private func panelEntries(featuredPacks: [FeaturedStickerPackItem], theme: PresentationTheme) -> [Entry] { - var entries: [Entry] = [] - var index = 0 - for pack in featuredPacks { - entries.append(.stickerPack(index: index, info: pack.info, topItem: pack.topItems.first, unread: pack.unread, theme: theme)) - index += 1 - } - return entries -} - -private final class FeaturedPackItem: ListViewItem { - let account: Account - let inputNodeInteraction: ChatMediaInputNodeInteraction - let collectionId: ItemCollectionId - let collectionInfo: StickerPackCollectionInfo - let stickerPackItem: StickerPackItem? - let unread: Bool - let selectedItem: () -> Void - let index: Int - let theme: PresentationTheme - let isVisible: () -> Bool - - var selectable: Bool { - return true - } - - init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, unread: Bool, index: Int, theme: PresentationTheme, selected: @escaping () -> Void, isVisible: @escaping () -> Bool) { - self.account = account - self.inputNodeInteraction = inputNodeInteraction - self.collectionId = collectionId - self.collectionInfo = collectionInfo - self.stickerPackItem = stickerPackItem - self.unread = unread - self.index = index - self.theme = theme - self.selectedItem = selected - self.isVisible = isVisible - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = FeaturedPackItemNode() - node.contentSize = boundingSize - node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) - node.inputNodeInteraction = self.inputNodeInteraction - node.panelIsVisible = self.isVisible - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in - node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme) - }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - completion(ListViewItemNodeLayout(contentSize: boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in - (node() as? FeaturedPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme) - }) - } - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private final class FeaturedPackItemNode: ListViewItemNode { - private let containerNode: ASDisplayNode - private let imageNode: TransformImageNode - private var animatedStickerNode: AnimatedStickerNode? - private var placeholderNode: StickerShimmerEffectNode? - private let unreadNode: ASImageNode - - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var currentCollectionId: ItemCollectionId? - private var currentThumbnailItem: StickerPackThumbnailItem? - private var theme: PresentationTheme? - - private let stickerFetchedDisposable = MetaDisposable() - - var panelIsVisible: () -> Bool = { - return true - } - - override var visibility: ListViewItemNodeVisibility { - didSet { - self.visibilityStatus = self.visibility != .none - } - } - - var visibilityStatus: Bool = false { - didSet { - if self.visibilityStatus != oldValue { - self.updateVisibility() - } - } - } - - func updateVisibility() { - let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false - let panelVisible = self.panelIsVisible() - self.animatedStickerNode?.visibility = self.visibilityStatus && loopAnimatedStickers && panelVisible - } - - init() { - self.containerNode = ASDisplayNode() - self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - self.imageNode = TransformImageNode() - self.imageNode.isLayerBacked = !smartInvertColorsEnabled() - - self.placeholderNode = StickerShimmerEffectNode() - - self.unreadNode = ASImageNode() - self.unreadNode.isLayerBacked = true - self.unreadNode.displayWithoutProcessing = true - self.unreadNode.displaysAsynchronously = false - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.containerNode) - - self.containerNode.addSubnode(self.imageNode) - if let placeholderNode = self.placeholderNode { - self.containerNode.addSubnode(placeholderNode) - } - self.containerNode.addSubnode(self.unreadNode) - - var firstTime = true - self.imageNode.imageUpdated = { [weak self] image in - guard let strongSelf = self else { - return - } - if image != nil { - strongSelf.removePlaceholder(animated: !firstTime) - if firstTime { - strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - firstTime = false - } - } - - deinit { - self.stickerFetchedDisposable.dispose() - } - - private func removePlaceholder(animated: Bool) { - if let placeholderNode = self.placeholderNode { - self.placeholderNode = nil - if !animated { - placeholderNode.removeFromSupernode() - } else { - placeholderNode.alpha = 0.0 - placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in - placeholderNode?.removeFromSupernode() - }) - } - } - } - - func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, unread: Bool, theme: PresentationTheme) { - self.currentCollectionId = collectionId - - if self.theme !== theme { - self.theme = theme - } - - var thumbnailItem: StickerPackThumbnailItem? - var resourceReference: MediaResourceReference? - if let thumbnail = info.thumbnail { - if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { - thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, info.flags.contains(.isVideo)) - } else { - thumbnailItem = .still(thumbnail) - } - resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource) - } else if let item = item { - if item.file.isAnimatedSticker || item.file.isVideoSticker { - thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100), item.file.isVideoSticker) - resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) - } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { - thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) - } - } - - var imageSize = boundingImageSize - - if self.currentThumbnailItem != thumbnailItem { - self.currentThumbnailItem = thumbnailItem - if let thumbnailItem = thumbnailItem { - switch thumbnailItem { - case let .still(representation): - imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize) - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) - imageApply() - self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true)) - case let .animated(resource, dimensions, isVideo): - imageSize = dimensions.cgSize.aspectFitted(boundingImageSize) - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) - imageApply() - self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true)) - - let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false - - let animatedStickerNode: AnimatedStickerNode - if let current = self.animatedStickerNode { - animatedStickerNode = current - } else { - animatedStickerNode = DefaultAnimatedStickerNodeImpl() - animatedStickerNode.started = { [weak self] in - self?.imageNode.isHidden = true - self?.removePlaceholder(animated: false) - } - self.animatedStickerNode = animatedStickerNode - if let placeholderNode = self.placeholderNode { - self.containerNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) - } else { - self.containerNode.addSubnode(animatedStickerNode) - } - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: 128, height: 128, playbackMode: .loop, mode: .cached) - } - animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers - } - if let resourceReference = resourceReference { - self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: resourceReference).start()) - } - } - } - - if let placeholderNode = self.placeholderNode { - var imageSize = PixelDimensions(width: 512, height: 512) - var immediateThumbnailData: Data? - if let data = info.immediateThumbnailData { - if info.flags.contains(.isVideo) { - imageSize = PixelDimensions(width: 100, height: 100) - } - immediateThumbnailData = data - } else if let data = item?.file.immediateThumbnailData { - immediateThumbnailData = data - } - - placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: immediateThumbnailData, size: boundingImageSize, enableEffect: true, imageSize: imageSize.cgSize) - } - - self.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize) - - self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize) - self.imageNode.position = CGPoint(x: boundingSize.height / 2.0, y: boundingSize.width / 2.0) - if let animatedStickerNode = self.animatedStickerNode { - animatedStickerNode.frame = self.imageNode.frame - animatedStickerNode.updateLayout(size: self.imageNode.frame.size) - } - if let placeholderNode = self.placeholderNode { - placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize) - placeholderNode.position = self.imageNode.position - } - - let unreadImage = PresentationResourcesItemList.stickerUnreadDotImage(theme) - if unread { - self.unreadNode.isHidden = false - } else { - self.unreadNode.isHidden = true - } - if let image = unreadImage { - self.unreadNode.image = image - self.unreadNode.frame = CGRect(origin: CGPoint(x: 35.0, y: 4.0), size: image.size) - } - } - - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { - if let placeholderNode = self.placeholderNode { - placeholderNode.updateAbsoluteRect(rect, within: containerSize) - } - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - } -} - - -final class StickerPaneTrendingListGridItem: GridItem { - let account: Account - let theme: PresentationTheme - let strings: PresentationStrings - let trendingPacks: [FeaturedStickerPackItem] - let isPremium: Bool - let inputNodeInteraction: ChatMediaInputNodeInteraction - let dismiss: (() -> Void)? - - let section: GridSection? = nil - let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? - - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, trendingPacks: [FeaturedStickerPackItem], isPremium: Bool, inputNodeInteraction: ChatMediaInputNodeInteraction, dismiss: (() -> Void)?) { - self.account = account - self.theme = theme - self.strings = strings - self.trendingPacks = trendingPacks - self.isPremium = isPremium - self.inputNodeInteraction = inputNodeInteraction - self.dismiss = dismiss - self.fillsRowWithDynamicHeight = { _ in - return 70.0 - } - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = StickerPaneTrendingListGridItemNode() - node.setup(item: self) - return node - } - - func update(node: GridItemNode) { - guard let node = node as? StickerPaneTrendingListGridItemNode else { - assertionFailure() - return - } - node.setup(item: self) - } -} - -private let titleFont = Font.medium(12.0) - -class StickerPaneTrendingListGridItemNode: GridItemNode { - private let titleNode: TextNode - private let dismissButtonNode: HighlightTrackingButtonNode - - private let listView: ListView - - private var item: StickerPaneTrendingListGridItem? - private var appliedItem: StickerPaneTrendingListGridItem? - - private var isPanelVisible = false - override var isVisibleInGrid: Bool { - didSet { - self.updateVisibility() - } - } - - private let disposable = MetaDisposable() - private var currentEntries: [Entry] = [] - - override init() { - self.titleNode = TextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.contentMode = .left - self.titleNode.contentsScale = UIScreen.main.scale - - self.dismissButtonNode = HighlightTrackingButtonNode() - - self.listView = ListView() - self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) - self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.listView) - self.addSubnode(self.dismissButtonNode) - - self.dismissButtonNode.addTarget(self, action: #selector(self.dismissPressed), forControlEvents: .touchUpInside) - } - - deinit { - self.disposable.dispose() - } - - private func enqueuePanelTransition(_ transition: Transition, firstTime: Bool) { - var options = ListViewDeleteAndInsertOptions() - if firstTime { - options.insert(.Synchronous) - options.insert(.LowLatency) - } else { - options.insert(.AnimateInsertion) - } - - self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateOpaqueState: nil, completion: { _ in }) - } - - func setup(item: StickerPaneTrendingListGridItem) { - self.item = item - - let entries = panelEntries(featuredPacks: item.trendingPacks, theme: item.theme) - let transition = preparedEntryTransition(account: item.account, from: self.currentEntries, to: entries, inputNodeInteraction: item.inputNodeInteraction, isVisible: { [weak self] in - if let strongSelf = self { - return strongSelf.isPanelVisible && strongSelf.isVisibleInGrid - } else { - return false - } - }) - self.enqueuePanelTransition(transition, firstTime: self.currentEntries.isEmpty) - self.currentEntries = entries - - self.setNeedsLayout() - } - - func updateIsPanelVisible(_ isPanelVisible: Bool) { - if self.isPanelVisible != isPanelVisible { - self.isPanelVisible = isPanelVisible - self.updateVisibility() - } - } - - func updateVisibility() { - self.listView.forEachItemNode { itemNode in - if let itemNode = itemNode as? FeaturedPackItemNode { - itemNode.updateVisibility() - } - } - } - - override func layout() { - super.layout() - guard let item = self.item else { - return - } - - let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.size.height) - - let makeTitleLayout = TextNode.asyncLayout(self.titleNode) - - let currentItem = self.appliedItem - self.appliedItem = item - - let width = self.bounds.size.width - - self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width) - self.listView.position = CGPoint(x: width / 2.0, y: 26.0 + 41.0 / 2.0) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: .immediate) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: self.bounds.size.width), insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), duration: duration, curve: curve) - - self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - if currentItem?.theme !== item.theme { - self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme, color: item.theme.chat.inputMediaPanel.stickersSectionTextColor), for: []) - } - - let leftInset: CGFloat = 9.0 - let rightInset: CGFloat = 18.0 + UIScreenPixel - let topOffset: CGFloat = 9.0 - - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: (item.isPremium ? item.strings.Stickers_TrendingPremiumStickers : item.strings.StickerPacksSettings_FeaturedPacks).uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - self.item = item - - let _ = titleApply() - - let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset), size: titleLayout.size) - let dismissButtonSize = CGSize(width: 12.0, height: 12.0) - self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width + 1.0, y: topOffset - 1.0), size: dismissButtonSize) - self.dismissButtonNode.isHidden = item.dismiss == nil - self.titleNode.frame = titleFrame - } - - @objc private func dismissPressed() { - if let item = self.item { - item.dismiss?() - } - } -} diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift deleted file mode 100644 index 9756e35f08..0000000000 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift +++ /dev/null @@ -1,239 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import StickerResources -import AccountContext -import ChatPresentationInterfaceState - -final class StickersChatInputContextPanelItem: ListViewItem { - let account: Account - let theme: PresentationTheme - let index: Int - let files: [TelegramMediaFile] - let itemsInRow: Int - let stickersInteraction: StickersChatInputContextPanelInteraction - let interfaceInteraction: ChatPanelInterfaceInteraction - - let selectable: Bool = false - - public init(account: Account, theme: PresentationTheme, index: Int, files: [TelegramMediaFile], itemsInRow: Int, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) { - self.account = account - self.theme = theme - self.index = index - self.files = files - self.itemsInRow = itemsInRow - self.stickersInteraction = stickersInteraction - self.interfaceInteraction = interfaceInteraction - } - - public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - let configure = { () -> Void in - let node = StickersChatInputContextPanelItemNode() - - let nodeLayout = node.asyncLayout() - let (top, bottom) = (previousItem != nil, nextItem != nil) - let (layout, apply) = nodeLayout(self, params, top, bottom) - - node.contentSize = layout.contentSize - node.insets = layout.insets - - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in apply(.None) }) - }) - } - } - if Thread.isMainThread { - async { - configure() - } - } else { - configure() - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - if let nodeValue = node() as? StickersChatInputContextPanelItemNode { - let nodeLayout = nodeValue.asyncLayout() - - async { - let (top, bottom) = (previousItem != nil, nextItem != nil) - - let (layout, apply) = nodeLayout(self, params, top, bottom) - Queue.mainQueue().async { - completion(layout, { _ in - apply(animation) - }) - } - } - } else { - assertionFailure() - } - } - } -} - -private let itemSize = CGSize(width: 66.0, height: 66.0) -private let inset: CGFloat = 3.0 - -final class StickersChatInputContextPanelItemNode: ListViewItemNode { - private let topSeparatorNode: ASDisplayNode - private var nodes: [TransformImageNode] = [] - private var item: StickersChatInputContextPanelItem? - private let disposables = DisposableSet() - - private var currentPreviewingIndex: Int? - - init() { - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - - super.init(layerBacked: false, dynamicBounce: false) - } - - deinit { - self.disposables.dispose() - } - - override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { - if let item = item as? StickersChatInputContextPanelItem { - let doLayout = self.asyncLayout() - let merged = (top: previousItem != nil, bottom: nextItem != nil) - let (layout, apply) = doLayout(item, params, merged.top, merged.bottom) - self.contentSize = layout.contentSize - self.insets = layout.insets - apply(.None) - } - } - - override func didLoad() { - super.didLoad() - - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) - } - - @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { - guard let item = self.item else { - return - } - let location = gestureRecognizer.location(in: gestureRecognizer.view) - for i in 0 ..< self.nodes.count { - if self.nodes[i].frame.contains(location) { - let file = item.files[i] - let _ = item.interfaceInteraction.sendSticker(.standalone(media: file), true, self.nodes[i].view, self.nodes[i].bounds, nil, []) - break - } - } - } - - func stickerItem(at index: Int) -> StickerPackItem? { - guard let item = self.item else { - return nil - } - if index < item.files.count { - return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.files[index], indexKeys: []) - } else { - return nil - } - } - - func stickerItem(at location: CGPoint) -> (StickerPackItem, ASDisplayNode)? { - guard let item = self.item else { - return nil - } - for i in 0 ..< self.nodes.count { - if self.nodes[i].frame.contains(location) { - return (StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.files[i], indexKeys: []), self.nodes[i]) - } - } - return nil - } - - func updatePreviewing(animated: Bool) { - guard let item = self.item else { - return - } - - var previewingIndex: Int? = nil - for i in 0 ..< item.files.count { - if item.stickersInteraction.previewedStickerItem?.fileId == self.stickerItem(at: i)?.file.fileId { - previewingIndex = i - break - } - } - - if self.currentPreviewingIndex != previewingIndex { - self.currentPreviewingIndex = previewingIndex - - for i in 0 ..< self.nodes.count { - let layer = self.nodes[i].layer - if i == previewingIndex { - layer.transform = CATransform3DMakeScale(0.8, 0.8, 1.0) - if animated { - let scale = ((layer.presentation()?.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue ?? (layer.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue) ?? 1.0 - layer.animateSpring(from: scale as NSNumber, to: 0.8 as NSNumber, keyPath: "transform.scale", duration: 0.4) - } - } else { - layer.transform = CATransform3DIdentity - if animated { - let scale = ((layer.presentation()?.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue ?? (layer.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue) ?? 0.8 - layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) - } - } - } - } - } - - func asyncLayout() -> (_ item: StickersChatInputContextPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { - return { [weak self] item, params, mergedTop, mergedBottom in - let baseWidth = params.width - params.leftInset - params.rightInset - let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 66.0), insets: UIEdgeInsets()) - - return (nodeLayout, { _ in - if let strongSelf = self { - strongSelf.backgroundColor = item.theme.list.plainBackgroundColor - strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.item = item - - if item.index == 0 && strongSelf.topSeparatorNode.supernode == nil { - strongSelf.addSubnode(strongSelf.topSeparatorNode) - } - strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) - - let spacing = (baseWidth - itemSize.width * CGFloat(item.itemsInRow)) / (CGFloat(max(1, item.itemsInRow + 1))) - - var i = 0 - for file in item.files { - let imageNode: TransformImageNode - if strongSelf.nodes.count > i { - imageNode = strongSelf.nodes[i] - } else { - imageNode = TransformImageNode() - strongSelf.nodes.append(imageNode) - strongSelf.addSubnode(imageNode) - } - - imageNode.setSignal(chatMessageSticker(account: item.account, userLocation: .other, file: file, small: true)) - strongSelf.disposables.add(freeMediaFileResourceInteractiveFetched(account: item.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: true)).start()) - - var imageSize = itemSize - if let dimensions = file.dimensions { - imageSize = dimensions.cgSize.aspectFitted(CGSize(width: itemSize.width - 4.0, height: itemSize.height - 4.0)) - imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - } - - imageNode.frame = CGRect(x: spacing + params.leftInset + (itemSize.width + spacing) * CGFloat(i) + floor((itemSize.width - imageSize.width) / 2.0), y: floor((itemSize.height - imageSize.height) / 2.0), width: imageSize.width, height: imageSize.height) - - i += 1 - } - } - }) - } - } -} diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift deleted file mode 100644 index f54acae3f8..0000000000 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ /dev/null @@ -1,406 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Postbox -import TelegramCore -import Display -import SwiftSignalKit -import TelegramPresentationData -import TelegramUIPreferences -import MergeLists -import AccountContext -import StickerPackPreviewUI -import StickerPeekUI -import ContextUI -import ChatPresentationInterfaceState -import PremiumUI -import UndoUI -import ChatControllerInteraction - -private struct StickersChatInputContextPanelEntryStableId: Hashable { - let ids: [MediaId] -} - -final class StickersChatInputContextPanelInteraction { - var previewedStickerItem: TelegramMediaFile? -} - -private struct StickersChatInputContextPanelEntry: Identifiable, Comparable { - let index: Int - let theme: PresentationTheme - let files: [TelegramMediaFile] - let itemsInRow: Int - - var stableId: StickersChatInputContextPanelEntryStableId { - return StickersChatInputContextPanelEntryStableId(ids: files.compactMap { $0.id }) - } - - static func ==(lhs: StickersChatInputContextPanelEntry, rhs: StickersChatInputContextPanelEntry) -> Bool { - return lhs.index == rhs.index && lhs.stableId == rhs.stableId - } - - static func <(lhs: StickersChatInputContextPanelEntry, rhs: StickersChatInputContextPanelEntry) -> Bool { - return lhs.index < rhs.index - } - - func withUpdatedTheme(_ theme: PresentationTheme) -> StickersChatInputContextPanelEntry { - return StickersChatInputContextPanelEntry(index: self.index, theme: theme, files: self.files, itemsInRow: itemsInRow) - } - - func item(account: Account, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> ListViewItem { - return StickersChatInputContextPanelItem(account: account, theme: self.theme, index: self.index, files: self.files, itemsInRow: self.itemsInRow, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction) - } -} - - -private struct StickersChatInputContextPanelTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] -} - -private func preparedTransition(from fromEntries: [StickersChatInputContextPanelEntry], to toEntries: [StickersChatInputContextPanelEntry], account: Account, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> StickersChatInputContextPanelTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction), directionHint: nil) } - - return StickersChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates) -} - -private let itemSize = CGSize(width: 66.0, height: 66.0) - -final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { - private let strings: PresentationStrings - - private let listView: ListView - private var results: [TelegramMediaFile] = [] - private var currentEntries: [StickersChatInputContextPanelEntry]? - - private var enqueuedTransitions: [(StickersChatInputContextPanelTransition, Bool)] = [] - private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState)? - - public var controllerInteraction: ChatControllerInteraction? - private let stickersInteraction: StickersChatInputContextPanelInteraction - - private var stickerPreviewController: StickerPreviewController? - - override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) { - self.strings = strings - - self.listView = ListView() - self.listView.isOpaque = false - self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor - self.listView.limitHitTestToNodes = true - self.listView.view.disablesInteractiveTransitionGestureRecognizer = true - self.listView.accessibilityPageScrolledString = { row, count in - return strings.VoiceOver_ScrollStatus(row, count).string - } - - self.stickersInteraction = StickersChatInputContextPanelInteraction() - - super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext) - - self.isOpaque = false - self.clipsToBounds = true - - self.addSubnode(self.listView) - } - - override func didLoad() { - super.didLoad() - - self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in - if let strongSelf = self { - let convertedPoint = strongSelf.listView.view.convert(point, from: strongSelf.view) - guard strongSelf.listView.bounds.contains(convertedPoint) else { - return nil - } - - var stickersNode: StickersChatInputContextPanelItemNode? - strongSelf.listView.forEachVisibleItemNode({ itemNode in - if itemNode.frame.contains(convertedPoint), let node = itemNode as? StickersChatInputContextPanelItemNode { - stickersNode = node - } - }) - - if let stickersNode = stickersNode { - let point = strongSelf.listView.view.convert(point, to: stickersNode.view) - if let (item, itemNode) = stickersNode.stickerItem(at: point) { - return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) - |> deliverOnMainQueue - |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var menuItems: [ContextMenuItem] = [] - menuItems = [ - .action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.default) - - let _ = controllerInteraction.sendSticker(.standalone(media: item.file), false, false, nil, true, itemNode.view, itemNode.bounds, nil, []) - })), - .action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) - - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let _ = (strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .generic: - strongSelf.interfaceInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) - case let .limitExceeded(limit, premiumLimit): - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) - let text: String - if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { - text = strongSelf.strings.Premium_MaxFavedStickersFinalText - } else { - text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string - } - strongSelf.interfaceInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: strongSelf.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { [weak self] action in - if let strongSelf = self { - if case .info = action { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers) - strongSelf.controllerInteraction?.navigationController()?.pushViewController(controller) - return true - } - } - return false - }), nil) - } - }) - } - })), - .action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) - - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - loop: for attribute in item.file.attributes { - switch attribute { - case let .Sticker(_, packReference, _): - if let packReference = packReference { - let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controllerInteraction.navigationController(), sendSticker: { file, sourceNode, sourceRect in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - return controllerInteraction.sendSticker(file, false, false, nil, true, sourceNode, sourceRect, nil, []) - } else { - return false - } - }) - - controllerInteraction.navigationController()?.view.window?.endEditing(true) - controllerInteraction.presentController(controller, nil) - } - break loop - default: - break - } - } - } - })) - ] - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in - guard let strongSelf = self else { - return - } - let controller = PremiumIntroScreen(context: strongSelf.context, source: .stickers) - strongSelf.controllerInteraction?.navigationController()?.pushViewController(controller) - })) - } else { - return nil - } - } - } - } - } - return nil - }, present: { [weak self] content, sourceView, sourceRect in - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceView: { - return (sourceView, sourceRect) - }) - strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil) - return controller - } - return nil - }, updateContent: { [weak self] content in - if let strongSelf = self { - var item: TelegramMediaFile? - if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item { - item = contentItem - } - strongSelf.updatePreviewingItem(file: item, animated: true) - } - })) - } - - private func updatePreviewingItem(file: TelegramMediaFile?, animated: Bool) { - if self.stickersInteraction.previewedStickerItem?.fileId != file?.fileId { - self.stickersInteraction.previewedStickerItem = file - - self.listView.forEachItemNode { itemNode in - if let itemNode = itemNode as? StickersChatInputContextPanelItemNode { - itemNode.updatePreviewing(animated: animated) - } - } - } - } - - func updateResults(_ results: [TelegramMediaFile]) { - self.results = results - - self.commitResults(updateLayout: true) - } - - private func commitResults(updateLayout: Bool = false) { - guard let validLayout = self.validLayout else { - return - } - - var entries: [StickersChatInputContextPanelEntry] = [] - - let itemsInRow = Int(floor((validLayout.0.width - validLayout.1 - validLayout.2) / itemSize.width)) - - var files: [TelegramMediaFile] = [] - var index = entries.count - for i in 0 ..< self.results.count { - files.append(results[i]) - if files.count == itemsInRow { - entries.append(StickersChatInputContextPanelEntry(index: index, theme: self.theme, files: files, itemsInRow: itemsInRow)) - index += 1 - files.removeAll() - } - } - - if !files.isEmpty { - entries.append(StickersChatInputContextPanelEntry(index: index, theme: self.theme, files: files, itemsInRow: itemsInRow)) - } - - if updateLayout { - self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate, interfaceState: validLayout.4) - } - - self.prepareTransition(from: self.currentEntries, to: entries) - } - - private func prepareTransition(from: [StickersChatInputContextPanelEntry]? , to: [StickersChatInputContextPanelEntry]) { - let firstTime = from == nil - let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, stickersInteraction: self.stickersInteraction, interfaceInteraction: self.interfaceInteraction!) - self.currentEntries = to - self.enqueueTransition(transition, firstTime: firstTime) - } - - private func enqueueTransition(_ transition: StickersChatInputContextPanelTransition, firstTime: Bool) { - self.enqueuedTransitions.append((transition, firstTime)) - - if self.validLayout != nil { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - } - - private func dequeueTransition() { - if let validLayout = self.validLayout, let (transition, firstTime) = self.enqueuedTransitions.first { - self.enqueuedTransitions.remove(at: 0) - - var options = ListViewDeleteAndInsertOptions() - if firstTime { - //options.insert(.Synchronous) - //options.insert(.LowLatency) - } else { - options.insert(.AnimateTopItemPosition) - options.insert(.AnimateCrossfade) - } - - var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0) - insets.left = validLayout.1 - insets.right = validLayout.2 - - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: insets, duration: 0.0, curve: .Default(duration: nil)) - - self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in - if let strongSelf = self, firstTime { - var topItemOffset: CGFloat? - strongSelf.listView.forEachItemNode { itemNode in - if topItemOffset == nil { - topItemOffset = itemNode.frame.minY - } - } - - if let topItemOffset = topItemOffset { - let position = strongSelf.listView.layer.position - strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)) - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView { - strongSelf.listView.position = position - } - } - } - }) - } - } - - private func topInsetForLayout(size: CGSize) -> CGFloat { - let minimumItemHeights: CGFloat = floor(itemSize.height * 1.5) - - return max(size.height - minimumItemHeights, 0.0) - } - - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { - let hadValidLayout = self.validLayout != nil - self.validLayout = (size, leftInset, rightInset, bottomInset, interfaceState) - - var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size) - insets.left = leftInset - insets.right = rightInset - - transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) - - self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - self.commitResults(updateLayout: false) - - if !hadValidLayout { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - - if self.theme !== interfaceState.theme { - self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor - - let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] - self.prepareTransition(from: self.currentEntries, to: new) - } - } - - override func animateOut(completion: @escaping () -> Void) { - var topItemOffset: CGFloat? - self.listView.forEachItemNode { itemNode in - if topItemOffset == nil { - topItemOffset = itemNode.frame.minY - } - } - - if let topItemOffset = topItemOffset { - let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - completion() - }) - } else { - completion() - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let listViewFrame = self.listView.frame - return self.listView.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event) - } -} diff --git a/submodules/TelegramUI/Sources/TelegramUI.h b/submodules/TelegramUI/Sources/TelegramUI.h deleted file mode 100644 index 6a7fcb9a8a..0000000000 --- a/submodules/TelegramUI/Sources/TelegramUI.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// TelegramUI.h -// TelegramUI -// -// Created by Peter on 5/3/17. -// Copyright © 2017 Telegram. All rights reserved. -// - -#import - -//! Project version number for TelegramUI. -FOUNDATION_EXPORT double TelegramUIVersionNumber; - -//! Project version string for TelegramUI. -FOUNDATION_EXPORT const unsigned char TelegramUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import -