import Foundation import UIKit import AsyncDisplayKit import Display import TelegramPresentationData import SearchBarNode private let searchBarFont = Font.regular(17.0) public let navigationBarSearchContentHeight: CGFloat = 54.0 public class NavigationBarSearchContentNode: NavigationBarContentNode { public var theme: PresentationTheme? public var placeholder: String public var compactPlaceholder: String private let inline: Bool public let placeholderNode: SearchBarPlaceholderNode public var placeholderHeight: CGFloat? private var disabledOverlay: ASDisplayNode? public var expansionProgress: CGFloat = 1.0 public var additionalHeight: CGFloat = 0.0 private var validLayout: (CGSize, CGFloat, CGFloat)? public init(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil, inline: Bool = false, activate: @escaping () -> Void) { self.theme = theme self.placeholder = placeholder self.compactPlaceholder = compactPlaceholder ?? placeholder self.inline = inline self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern) self.placeholderNode.labelNode.displaysAsynchronously = false super.init() self.placeholderNode.isAccessibilityElement = true self.placeholderNode.accessibilityLabel = placeholder self.placeholderNode.accessibilityTraits = .searchField self.addSubnode(self.placeholderNode) self.placeholderNode.activate = activate } public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil) { self.theme = theme self.placeholder = placeholder self.compactPlaceholder = compactPlaceholder ?? placeholder self.placeholderNode.accessibilityLabel = placeholder if let disabledOverlay = self.disabledOverlay { disabledOverlay.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor.withAlphaComponent(0.5) } if let validLayout = self.validLayout { self.updatePlaceholder(self.expansionProgress, size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate) } } public func updateListVisibleContentOffset(_ offset: ListViewVisibleContentOffset, transition: ContainedViewLayoutTransition = .immediate) { var progress: CGFloat = 0.0 switch offset { case let .known(offset): progress = max(0.0, (self.nominalHeight - offset)) / self.nominalHeight case .none: progress = 1.0 default: break } self.updateExpansionProgress(progress, animated: transition.isAnimated) } public func updateGridVisibleContentOffset(_ offset: GridNodeVisibleContentOffset) { var progress: CGFloat = 0.0 switch offset { case let .known(offset): progress = max(0.0, (self.nominalHeight - offset)) / self.nominalHeight case .none: progress = 1.0 default: break } self.updateExpansionProgress(progress) } public func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) { let newProgress = max(0.0, min(10.0, progress)) if abs(newProgress - self.expansionProgress) > 0.0001 { self.expansionProgress = newProgress let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: ContainedViewLayoutTransitionCurve.slide) : .immediate if let validLayout = self.validLayout, animated { self.updatePlaceholder(self.expansionProgress, size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: transition) } self.requestContainerLayout(transition) } } public func setIsEnabled(_ enabled: Bool, animated: Bool = false) { let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate transition.updateAlpha(node: self.placeholderNode, alpha: enabled ? 1.0 : 0.6) self.placeholderNode.isUserInteractionEnabled = enabled } private func updatePlaceholder(_ progress: CGFloat, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { let padding: CGFloat = 10.0 let baseWidth = size.width - padding * 2.0 - leftInset - rightInset let fieldHeight: CGFloat = 36.0 let fraction = fieldHeight / self.nominalHeight let fullFraction = navigationBarSearchContentHeight / self.nominalHeight let fromLow: CGFloat = fullFraction - fraction let toLow: CGFloat = 0.0 let fromHigh: CGFloat = fullFraction let toHigh: CGFloat = 1.0 var visibleProgress: CGFloat = toLow + (self.expansionProgress - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) visibleProgress = max(0.0, min(1.0, visibleProgress)) let searchBarNodeLayout = self.placeholderNode.asyncLayout() let textColor = self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93) var fillColor = self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear if self.inline, let theme = self.theme, fillColor.distance(to: theme.list.blocksBackgroundColor) < 100 { fillColor = fillColor.withMultipliedBrightnessBy(0.8) } let backgroundColor = self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear let placeholderString = NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: textColor) let compactPlaceholderString = NSAttributedString(string: self.compactPlaceholder, font: searchBarFont, textColor: textColor) let (searchBarHeight, searchBarApply) = searchBarNodeLayout(placeholderString, compactPlaceholderString, CGSize(width: baseWidth, height: fieldHeight), visibleProgress, textColor, fillColor, backgroundColor, transition) searchBarApply() let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: size.height + (1.0 - visibleProgress) * fieldHeight - 8.0 - fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight)) transition.updateFrame(node: self.placeholderNode, frame: searchBarFrame) self.placeholderHeight = searchBarHeight if let disabledOverlay = self.disabledOverlay { var disabledOverlayFrame = self.placeholderNode.frame disabledOverlayFrame.size.height = searchBarHeight transition.updateFrame(node: disabledOverlay, frame: disabledOverlayFrame) } } override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (size, leftInset, rightInset) self.updatePlaceholder(self.expansionProgress, size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) } override public var height: CGFloat { return self.nominalHeight * self.expansionProgress } override public var clippedHeight: CGFloat { return self.nominalHeight * min(1.0, self.expansionProgress) } override public var nominalHeight: CGFloat { return navigationBarSearchContentHeight + self.additionalHeight } override public var mode: NavigationBarContentMode { return .expansion } }