import Foundation import UIKit import Display import AsyncDisplayKit import TelegramCore import SwiftSignalKit import TelegramPresentationData import ItemListUI import PresentationDataUtils import AccountContext import SearchBarNode import GlassBackgroundComponent import ComponentFlow import ComponentDisplayAdapters import AppBundle import ActivityIndicator final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch { let context: AccountContext let peers: [EnginePeer] let cancel: () -> Void let dismissInput: () -> Void let openPeer: (EnginePeer) -> Void init(context: AccountContext, peers: [EnginePeer], cancel: @escaping () -> Void, dismissInput: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.peers = peers self.cancel = cancel self.dismissInput = dismissInput self.openPeer = openPeer } func isEqual(to: ItemListControllerSearch) -> Bool { if let to = to as? ChannelDiscussionGroupSetupSearchItem { if self.context !== to.context { return false } if self.peers.count != to.peers.count { return false } return true } else { return false } } func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? ChannelDiscussionSearchNavigationContentNode { current.updateTheme(presentationData.theme) return current } else { return ChannelDiscussionSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, updateActivity: { _ in }) } } func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode { return ChannelDiscussionGroupSetupSearchItemNode(context: self.context, peers: self.peers, openPeer: self.openPeer, cancel: self.cancel, updateActivity: { _ in }, dismissInput: self.dismissInput) } } private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControllerSearchNode { private let containerNode: ChannelDiscussionGroupSearchContainerNode init(context: AccountContext, peers: [EnginePeer], openPeer: @escaping (EnginePeer) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping (Bool) -> Void, dismissInput: @escaping () -> Void) { self.containerNode = ChannelDiscussionGroupSearchContainerNode(context: context, peers: peers, openPeer: { peer in openPeer(peer) }) self.containerNode.dismissInput = { dismissInput() } self.containerNode.cancel = { cancel() } super.init() self.addSubnode(self.containerNode) } override func queryUpdated(_ query: String) { self.containerNode.searchTextUpdated(text: query) } override func scrollToTop() { self.containerNode.scrollToTop() } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.containerNode.hitTest(self.view.convert(point, to: self.containerNode.view), with: event) { return result } return super.hitTest(point, with: event) } } private let searchBarFont = Font.regular(17.0) private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode { private struct Params: Equatable { let size: CGSize let leftInset: CGFloat let rightInset: CGFloat init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.size = size self.leftInset = leftInset self.rightInset = rightInset } } private var theme: PresentationTheme private let strings: PresentationStrings private let cancel: () -> Void private let backgroundContainer: GlassBackgroundContainerView private let backgroundView: GlassBackgroundView private let iconView: UIImageView private var activityIndicator: ActivityIndicator? private let searchBar: SearchBarNode private let close: (background: GlassBackgroundView, icon: UIImageView) private var params: Params? private var queryUpdated: ((String) -> Void)? var activity: Bool = false { didSet { if self.activity != oldValue { if let params = self.params { self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } } } } init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { self.theme = theme self.strings = strings self.cancel = cancel self.backgroundContainer = GlassBackgroundContainerView() self.backgroundView = GlassBackgroundView() self.backgroundContainer.contentView.addSubview(self.backgroundView) self.iconView = UIImageView() self.backgroundView.contentView.addSubview(self.iconView) self.close = (GlassBackgroundView(), UIImageView()) self.close.background.contentView.addSubview(self.close.icon) self.searchBar = SearchBarNode( theme: SearchBarNodeTheme( background: .clear, separator: .clear, inputFill: .clear, primaryText: theme.chat.inputPanel.panelControlColor, placeholder: theme.chat.inputPanel.inputPlaceholderColor, inputIcon: theme.chat.inputPanel.inputControlColor, inputClear: theme.chat.inputPanel.panelControlColor, accent: theme.chat.inputPanel.panelControlAccentColor, keyboard: theme.rootController.keyboardColor ), presentationTheme: theme, strings: strings, fieldStyle: .inlineNavigation, forceSeparator: false, displayBackground: false, cancelText: nil ) super.init() self.view.addSubview(self.backgroundContainer) self.backgroundView.contentView.addSubview(self.searchBar.view) self.backgroundContainer.contentView.addSubview(self.close.background) self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) self?.cancel() } self.searchBar.textUpdated = { [weak self] query, _ in self?.queryUpdated?(query) } updateActivity({ [weak self] value in self?.activity = value }) self.updatePlaceholder() } @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.searchBar.cancel?() } } func setQueryUpdated(_ f: @escaping (String) -> Void) { self.queryUpdated = f } func updateTheme(_ theme: PresentationTheme) { self.theme = theme if let params = self.params { self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) } self.updatePlaceholder() } func updatePlaceholder() { let placeholderText: String placeholderText = self.strings.Channel_DiscussionGroup_SearchPlaceholder self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: self.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) } override var nominalHeight: CGFloat { return 60.0 } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) let transition = ComponentTransition(transition) let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0)) let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0)) transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition) transition.setFrame(view: self.backgroundView, frame: backgroundFrame) self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) if self.iconView.image == nil { self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) } transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor) if let image = self.iconView.image { let imageSize: CGSize let iconFrame: CGRect let iconFraction: CGFloat = 0.8 imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize) transition.setPosition(view: self.iconView, position: iconFrame.center) transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) } if self.activity { let activityIndicator: ActivityIndicator if let current = self.activityIndicator { activityIndicator = current } else { activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false)) self.activityIndicator = activityIndicator self.backgroundView.contentView.addSubview(activityIndicator.view) } let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize) transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center) transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) } else if let activityIndicator = self.activityIndicator { self.activityIndicator = nil activityIndicator.view.removeFromSuperview() } self.iconView.isHidden = self.activity let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0)) transition.setFrame(view: self.searchBar.view, frame: searchBarFrame) self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) if self.close.icon.image == nil { self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setLineWidth(2.0) context.setLineCap(.round) context.setStrokeColor(UIColor.white.cgColor) context.beginPath() context.move(to: CGPoint(x: 12.0, y: 12.0)) context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) context.strokePath() })?.withRenderingMode(.alwaysTemplate) } if let image = close.icon.image { self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) } self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor transition.setFrame(view: self.close.background, frame: closeFrame) self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) } func activate() { self.searchBar.activate() } func deactivate() { self.searchBar.deactivate(clear: false) } }