import Foundation import UIKit import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData import ComponentFlow import MultilineTextComponent import BundleIconComponent import PlainButtonComponent private final class WeakController { private weak var _value: MinimizableController? public var value: MinimizableController? { return self._value } public init(_ value: MinimizableController) { self._value = value } } final class MinimizedHeaderNode: ASDisplayNode { var theme: NavigationControllerTheme { didSet { self.backgroundView.backgroundColor = self.theme.navigationBar.opaqueBackgroundColor self.progressView.backgroundColor = self.theme.navigationBar.primaryTextColor.withAlphaComponent(0.06) self.iconView.tintColor = self.theme.navigationBar.primaryTextColor } } let strings: PresentationStrings private let backgroundView = UIView() private let progressView = UIView() private var iconView = UIImageView() private let titleLabel = ComponentView() private let closeButton = ComponentView() private var titleDisposable: Disposable? private var _controllers: [WeakController] = [] var controllers: [MinimizableController] { get { return self._controllers.compactMap { $0.value } } set { if !newValue.isEmpty { if self.controllers.count == 1, let icon = self.controllers.first?.minimizedIcon { self.icon = icon } else { self.icon = nil } if self.controllers.count == 1, let progress = self.controllers.first?.minimizedProgress { self.progress = progress } else { self.progress = nil } if newValue.count != self.controllers.count { self._controllers = newValue.map { WeakController($0) } self.titleDisposable?.dispose() self.titleDisposable = nil var signals: [Signal] = [] for controller in newValue { signals.append(controller.titleSignal) } self.titleDisposable = (combineLatest(signals) |> deliverOnMainQueue).start(next: { [weak self] titles in guard let self else { return } let titles = titles.compactMap { $0 }.filter { !$0.isEmpty } if titles.count == 1, let title = titles.first { self.title = title } else if let title = titles.last { var trimmedTitle = title if trimmedTitle.count > 20 { trimmedTitle = "\(trimmedTitle.prefix(20).trimmingCharacters(in: .whitespacesAndNewlines))\u{2026}" } let othersString = self.strings.WebApp_MinimizedTitle_Others(Int32(titles.count - 1)) self.title = self.strings.WebApp_MinimizedTitleFormat(trimmedTitle, othersString).string } else { self.title = nil } }) } } else { self.icon = nil self.titleDisposable?.dispose() self.titleDisposable = nil } } } var icon: UIImage? { didSet { self.iconView.image = self.icon if let (size, insets, isExpanded) = self.validLayout { self.update(size: size, insets: insets, isExpanded: isExpanded, transition: .immediate) } } } var progress: Float? { didSet { if let (size, insets, isExpanded) = self.validLayout { self.update(size: size, insets: insets, isExpanded: isExpanded, transition: .immediate) } } } var title: String? { didSet { if let (size, insets, isExpanded) = self.validLayout { self.update(size: size, insets: insets, isExpanded: isExpanded, transition: .immediate) } } } var requestClose: () -> Void = {} var requestMaximize: () -> Void = {} private var validLayout: (CGSize, UIEdgeInsets, Bool)? init(theme: NavigationControllerTheme, strings: PresentationStrings) { self.theme = theme self.strings = strings self.backgroundView.clipsToBounds = true self.backgroundView.backgroundColor = self.theme.navigationBar.opaqueBackgroundColor self.backgroundView.layer.cornerRadius = 10.0 if #available(iOS 11.0, *) { self.backgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } self.progressView.backgroundColor = self.theme.navigationBar.primaryTextColor.withAlphaComponent(0.06) self.iconView.contentMode = .scaleAspectFit self.iconView.clipsToBounds = true self.iconView.layer.cornerRadius = 2.5 self.iconView.tintColor = self.theme.navigationBar.primaryTextColor super.init() self.clipsToBounds = true self.view.addSubview(self.backgroundView) self.backgroundView.addSubview(self.progressView) self.backgroundView.addSubview(self.iconView) applySmoothRoundedCorners(self.backgroundView.layer) } override func didLoad() { super.didLoad() self.backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.maximizeTapGesture(_:)))) } @objc private func maximizeTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { let location = recognizer.location(in: self.view) if location.x < 48.0 { self.requestClose() } else { self.requestMaximize() } } } func update(size: CGSize, insets: UIEdgeInsets, isExpanded: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = (size, insets, isExpanded) let headerHeight: CGFloat = 44.0 let titleSpacing: CGFloat = 6.0 var titleSideInset: CGFloat = 56.0 if !isExpanded { titleSideInset += insets.left } let iconSize = CGSize(width: 20.0, height: 20.0) let titleSize = self.titleLabel.update( transition: .immediate, component: AnyComponent( MultilineTextComponent(text: .plain(NSAttributedString(string: self.title ?? "", font: Font.bold(17.0), textColor: self.theme.navigationBar.primaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 1) ), environment: {}, containerSize: CGSize(width: size.width - titleSideInset * 2.0, height: headerHeight) ) var totalWidth = titleSize.width if isExpanded, let icon = self.icon { self.iconView.image = icon totalWidth += iconSize.width + titleSpacing } else { self.iconView.image = nil } let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: floorToScreenPixels((headerHeight - iconSize.height) / 2.0)), size: iconSize) self.iconView.frame = iconFrame let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0) + totalWidth - titleSize.width, y: floorToScreenPixels((headerHeight - titleSize.height) / 2.0)), size: titleSize) if let view = self.titleLabel.view { if view.superview == nil { self.backgroundView.addSubview(view) } view.bounds = CGRect(origin: .zero, size: titleFrame.size) transition.updatePosition(layer: view.layer, position: titleFrame.center) } let _ = self.closeButton.update( transition: .immediate, component: AnyComponent( PlainButtonComponent( content: AnyComponent( BundleIconComponent( name: "Instant View/Close", tintColor: self.theme.navigationBar.primaryTextColor ) ), effectAlignment: .center, minSize: CGSize(width: 44.0, height: 44.0), action: { [weak self] in self?.requestClose() }, animateScale: false ) ), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) let closeButtonFrame = CGRect(origin: CGPoint(x: isExpanded ? 0.0 : insets.left, y: 0.0), size: CGSize(width: 44.0, height: 44.0)) if let view = self.closeButton.view { if view.superview == nil { self.backgroundView.addSubview(view) } transition.updateFrame(view: view, frame: closeButtonFrame) } transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: size.width, height: 243.0))) transition.updateAlpha(layer: self.progressView.layer, alpha: isExpanded && self.progress != nil ? 1.0 : 0.0) if let progress = self.progress { self.progressView.frame = CGRect(origin: .zero, size: CGSize(width: size.width * CGFloat(progress), height: 243.0)) } } }