import Foundation import UIKit import Display import QuickLook import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore import SyncCore import LegacyComponents import TelegramPresentationData import AccountContext import GalleryUI import TelegramUniversalVideoContent final class WebSearchGalleryControllerInteraction { let dismiss: (Bool) -> Void let send: (ChatContextResult) -> Void let selectionState: TGMediaSelectionContext? let editingState: TGMediaEditingContext init(dismiss: @escaping (Bool) -> Void, send: @escaping (ChatContextResult) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { self.dismiss = dismiss self.send = send self.selectionState = selectionState self.editingState = editingState } } struct WebSearchGalleryEntry: Equatable { let index: Int let result: ChatContextResult static func ==(lhs: WebSearchGalleryEntry, rhs: WebSearchGalleryEntry) -> Bool { return lhs.result == rhs.result } func item(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchGalleryControllerInteraction?) -> GalleryItem { switch self.result { case let .externalReference(_, _, type, _, _, _, content, thumbnail, _): if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions { let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])) return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction) } case let .internalReference(_, _, _, _, _, _, file, _): if let file = file { return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction) } } preconditionFailure() } } final class WebSearchGalleryControllerPresentationArguments { let animated: Bool let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments? init(animated: Bool = true, transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) { self.animated = animated self.transitionArguments = transitionArguments } } class WebSearchGalleryController: ViewController { private static let navigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode } private let context: AccountContext private var presentationData: PresentationData private var controllerInteraction: WebSearchGalleryControllerInteraction? private let _ready = Promise() override var ready: Promise { return self._ready } private var didSetReady = false private let disposable = MetaDisposable() private var entries: [WebSearchGalleryEntry] = [] private var centralEntryIndex: Int? private let centralItemTitle = Promise() private let centralItemTitleView = Promise() private let centralItemNavigationStyle = Promise() private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() private let centralItemAttributesDisposable = DisposableSet(); private let checkedDisposable = MetaDisposable() private var checkNode: GalleryNavigationCheckNode? private let _hiddenMedia = Promise(nil) var hiddenMedia: Signal { return self._hiddenMedia.get() } private let replaceRootController: (ViewController, ValuePromise?) -> Void private let baseNavigationController: NavigationController? init(context: AccountContext, peer: Peer?, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?, sendCurrent: @escaping (ChatContextResult) -> Void) { self.context = context self.replaceRootController = replaceRootController self.baseNavigationController = baseNavigationController self.presentationData = context.sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: WebSearchGalleryController.navigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) self.controllerInteraction = WebSearchGalleryControllerInteraction(dismiss: { [weak self] animated in self?.dismiss(forceAway: false) }, send: { [weak self] current in sendCurrent(current) self?.dismiss(forceAway: true) }, selectionState: selectionState, editingState: editingState) if let title = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) { let recipientNode = GalleryNavigationRecipientNode(color: .white, title: title) let leftItem = UIBarButtonItem(customDisplayNode: recipientNode) self.navigationItem.leftBarButtonItem = leftItem } let checkNode = GalleryNavigationCheckNode(theme: self.presentationData.theme) checkNode.addTarget(target: self, action: #selector(self.checkPressed)) let rightItem = UIBarButtonItem(customDisplayNode: checkNode) self.navigationItem.rightBarButtonItem = rightItem self.checkNode = checkNode self.statusBar.statusBarStyle = .White let entriesSignal: Signal<[WebSearchGalleryEntry], NoError> = .single(entries) self.disposable.set((entriesSignal |> deliverOnMainQueue).start(next: { [weak self] entries in if let strongSelf = self { strongSelf.entries = entries strongSelf.centralEntryIndex = centralIndex if strongSelf.isViewLoaded { strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ $0.item(context: context, presentationData: strongSelf.presentationData, controllerInteraction: strongSelf.controllerInteraction) }), centralItemIndex: centralIndex, keepFirst: false) let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in strongSelf?.didSetReady = true } strongSelf._ready.set(ready |> map { true }) } } })) self.centralItemAttributesDisposable.add(self.centralItemTitle.get().start(next: { [weak self] title in self?.navigationItem.title = title })) self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in self?.navigationItem.titleView = titleView })) self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in self?.galleryNode.updatePresentationState({ $0.withUpdatedFooterContentNode(footerContentNode) }, transition: .immediate) })) } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { self.disposable.dispose() self.checkedDisposable.dispose() self.centralItemAttributesDisposable.dispose() } @objc func checkPressed() { if let checkNode = self.checkNode, let controllerInteraction = self.controllerInteraction, let centralItemNode = self.galleryNode.pager.centralItemNode() as? WebSearchVideoGalleryItemNode, let item = centralItemNode.item { let legacyItem = LegacyWebSearchItem(result: item.result) checkNode.setIsChecked(!checkNode.isChecked, animated: true) controllerInteraction.selectionState?.setItem(legacyItem, selected: checkNode.isChecked) } } private func dismiss(forceAway: Bool, animated: Bool = true) { var animatedOutNode = true var animatedOutInterface = false let completion = { [weak self] in if animatedOutNode && animatedOutInterface { self?._hiddenMedia.set(.single(nil)) self?.presentingViewController?.dismiss(animated: false, completion: nil) } } if animated { if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments { if !self.entries.isEmpty { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway { animatedOutNode = false centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { animatedOutNode = true completion() }) } } } self.galleryNode.animateOut(animateContent: animatedOutNode, completion: { animatedOutInterface = true completion() }) } else { animatedOutInterface = true completion() } } override func loadDisplayNode() { let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in if let strongSelf = self { strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true) } }, dismissController: { [weak self] in self?.dismiss(forceAway: true) }, replaceRootController: { [weak self] controller, ready in if let strongSelf = self { strongSelf.replaceRootController(controller, ready) } }) self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction) self.displayNodeDidLoad() self.galleryNode.statusBar = self.statusBar self.galleryNode.navigationBar = self.navigationBar self.galleryNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? WebSearchGalleryControllerPresentationArguments { if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) { return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) } } } return nil } self.galleryNode.dismiss = { [weak self] in self?._hiddenMedia.set(.single(nil)) self?.presentingViewController?.dismiss(animated: false, completion: nil) } self.galleryNode.pager.replaceItems(self.entries.map({ $0.item(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction) }), centralItemIndex: self.centralEntryIndex) self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in if let strongSelf = self { var item: WebSearchGalleryEntry? if let index = index { item = strongSelf.entries[index] if let node = strongSelf.galleryNode.pager.centralItemNode() { strongSelf.centralItemTitle.set(node.title()) strongSelf.centralItemTitleView.set(node.titleView()) strongSelf.centralItemNavigationStyle.set(node.navigationStyle()) strongSelf.centralItemFooterContentNode.set(node.footerContent()) } if let checkNode = strongSelf.checkNode, let controllerInteraction = strongSelf.controllerInteraction, let selectionState = controllerInteraction.selectionState, let item = item { checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: false) } } if strongSelf.didSetReady { strongSelf._hiddenMedia.set(.single(item)) } } } let selectionState = self.controllerInteraction?.selectionState let selectionUpdated = Signal { subscriber in if let selectionState = selectionState { let disposable = selectionState.selectionChangedSignal()!.start(next: { _ in subscriber.putNext(Void()) }, error: { _ in }, completed: {})! return ActionDisposable { disposable.dispose() } } else { subscriber.putCompletion() return EmptyDisposable } } self.checkedDisposable.set((selectionUpdated |> deliverOnMainQueue).start(next: { [weak self] _ in if let strongSelf = self, let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() { let item = strongSelf.entries[centralItemNode.index] if let checkNode = strongSelf.checkNode, let controllerInteraction = strongSelf.controllerInteraction, let selectionState = controllerInteraction.selectionState { checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: true) } } })) let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in self?.didSetReady = true } self._ready.set(ready |> map { true }) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) var nodeAnimatesItself = false if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments { self.centralItemTitle.set(centralItemNode.title()) self.centralItemTitleView.set(centralItemNode.titleView()) self.centralItemNavigationStyle.set(centralItemNode.navigationStyle()) self.centralItemFooterContentNode.set(centralItemNode.footerContent()) let item = self.entries[centralItemNode.index] if let transitionArguments = presentationArguments.transitionArguments(item) { nodeAnimatesItself = true centralItemNode.activateAsInitial() if presentationArguments.animated { centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) } if let checkNode = self.checkNode, let controllerInteraction = self.controllerInteraction, let selectionState = controllerInteraction.selectionState { checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: false) } self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) } } self.galleryNode.animateIn(animateContent: !nodeAnimatesItself) } override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.galleryNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } }