Swiftgram/TelegramUI/GifPaneSearchContentNode.swift
2019-03-09 18:52:31 +03:00

266 lines
11 KiB
Swift

import Foundation
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private let trendingGifsPromise = Promise<[FileMediaReference]?>(nil)
final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
private let context: AccountContext
private let controllerInteraction: ChatControllerInteraction
private let inputNodeInteraction: ChatMediaInputNodeInteraction
private var theme: PresentationTheme
private var strings: PresentationStrings
private var multiplexedNode: MultiplexedVideoNode?
private var validLayout: CGSize?
private let searchDisposable = MetaDisposable()
private let _ready = Promise<Void>()
var ready: Signal<Void, NoError> {
return self._ready.get()
}
var deactivateSearchBar: (() -> Void)?
var updateActivity: ((Bool) -> Void)?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) {
self.context = context
self.controllerInteraction = controllerInteraction
self.inputNodeInteraction = inputNodeInteraction
self.theme = theme
self.strings = strings
super.init()
let _ = (trendingGifsPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { next in
if next == nil {
trendingGifsPromise.set(self.signalForQuery(""))
}
})
self._ready.set(.single(Void()))
self.updateThemeAndStrings(theme: theme, strings: strings)
}
deinit {
self.searchDisposable.dispose()
}
func updateText(_ text: String) {
let signal: Signal<[FileMediaReference]?, NoError>
if !text.isEmpty {
signal = self.signalForQuery(text)
self.updateActivity?(true)
} else {
signal = trendingGifsPromise.get()
self.updateActivity?(false)
}
self.searchDisposable.set((signal
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self, let result = result else {
return
}
strongSelf.multiplexedNode?.files = result
strongSelf.updateActivity?(false)
}))
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
}
func updatePreviewing(animated: Bool) {
}
func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPreviewPeekItem)? {
return nil
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil
self.validLayout = size
// if let image = self.notFoundNode.image {
// let areaHeight = size.height - inputHeight
//
// let labelSize = self.notFoundLabel.updateLayout(CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude))
//
// transition.updateFrame(node: self.notFoundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((areaHeight - image.size.height - labelSize.height) / 2.0)), size: image.size))
// transition.updateFrame(node: self.notFoundLabel, frame: CGRect(origin: CGPoint(x: floor((image.size.width - labelSize.width) / 2.0), y: image.size.height + 8.0), size: labelSize))
// }
let contentFrame = CGRect(origin: CGPoint(), size: size)
if let multiplexedNode = self.multiplexedNode {
multiplexedNode.topInset = 0.0
multiplexedNode.bottomInset = 0.0
let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
transition.updateFrame(layer: multiplexedNode.layer, frame: nodeFrame)
multiplexedNode.updateLayout(size: nodeFrame.size, transition: transition)
}
if firstLayout {
self.updateText("")
}
}
override func willEnterHierarchy() {
super.willEnterHierarchy()
if self.multiplexedNode == nil {
let multiplexedNode = MultiplexedVideoNode(account: self.context.account)
self.multiplexedNode = multiplexedNode
if let layout = self.validLayout {
multiplexedNode.frame = CGRect(origin: CGPoint(), size: layout)
}
self.view.addSubview(multiplexedNode)
let gifs = self.context.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)])
|> map { view -> [FileMediaReference] in
var recentGifs: OrderedItemListView?
if let orderedView = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)] {
recentGifs = orderedView as? OrderedItemListView
}
if let recentGifs = recentGifs {
return recentGifs.items.map { item in
let file = (item.contents as! RecentMediaItem).media as! TelegramMediaFile
return .savedGif(media: file)
}
} else {
return []
}
}
self.searchDisposable.set((gifs |> deliverOnMainQueue).start(next: { [weak self] gifs in
if let strongSelf = self {
let previousFiles = strongSelf.multiplexedNode?.files
strongSelf.multiplexedNode?.files = gifs
if (previousFiles ?? []).isEmpty {
strongSelf.multiplexedNode?.contentOffset = CGPoint(x: 0.0, y: 60.0)
}
}
}))
multiplexedNode.fileSelected = { [weak self] fileReference in
self?.controllerInteraction.sendGif(fileReference)
}
multiplexedNode.didScroll = { [weak self] offset, height in
self?.deactivateSearchBar?()
}
}
}
func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) {
guard let multiplexedNode = self.multiplexedNode else {
return
}
multiplexedNode.alpha = 0.0
transition.updateAlpha(layer: multiplexedNode.layer, alpha: 1.0, completion: { _ in
})
if case let .animated(duration, curve) = transition {
multiplexedNode.layer.animatePosition(from: CGPoint(x: 0.0, y: additivePosition), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, additive: true)
}
}
func animateOut(transition: ContainedViewLayoutTransition) {
guard let multiplexedNode = self.multiplexedNode else {
return
}
transition.updateAlpha(layer: multiplexedNode.layer, alpha: 0.0, completion: { _ in
})
}
private func signalForQuery(_ query: String) -> Signal<[FileMediaReference]?, NoError> {
let delayRequest = true
let account = self.context.account
let contextBot = resolvePeerByName(account: account, name: "gif")
|> mapToSignal { peerId -> Signal<Peer?, NoError> in
if let peerId = peerId {
return account.postbox.loadedPeerWithId(peerId)
|> map { peer -> Peer? in
return peer
}
|> take(1)
} else {
return .single(nil)
}
}
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
let results = requestContextResults(account: account, botId: user.id, query: query, peerId: self.context.account.peerId, limit: 64)
|> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in
return .contextRequestResult(user, results)
}
}
let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ previousResult in
var passthroughPreviousResult: ChatContextResultCollection?
if let previousResult = previousResult {
if case let .contextRequestResult(previousUser, previousResults) = previousResult {
if previousUser?.id == user.id {
passthroughPreviousResult = previousResults
}
}
}
return .contextRequestResult(nil, passthroughPreviousResult)
})
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>
if delayRequest {
maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue())
} else {
maybeDelayedContextResults = results
}
return botResult |> then(maybeDelayedContextResults)
} else {
return .single({ _ in return nil })
}
}
return contextBot
|> mapToSignal { result -> Signal<[FileMediaReference]?, NoError> in
if let r = result(nil), case let .contextRequestResult(_, collection) = r, let results = collection?.results {
var references: [FileMediaReference] = []
for result in results {
switch result {
case let .internalReference(_, _, _, _, _, _, file, _):
if let file = file {
references.append(FileMediaReference.standalone(media: file))
}
default:
break
}
}
return .single(references)
} else {
return .complete()
}
}
|> deliverOnMainQueue
|> beforeStarted { [weak self] in
self?.updateActivity?(true)
}
|> afterCompleted { [weak self] in
self?.updateActivity?(false)
}
}
}