import Foundation import UIKit import Display import ComponentFlow import TelegramPresentationData import GlassBackgroundComponent public final class HeaderPanelContainerComponent: Component { public final class Panel: Equatable { public let key: AnyHashable public let orderIndex: Int public let component: AnyComponent public init(key: AnyHashable, orderIndex: Int, component: AnyComponent) { self.key = key self.orderIndex = orderIndex self.component = component } public static func ==(lhs: Panel, rhs: Panel) -> Bool { if lhs.key != rhs.key { return false } if lhs.orderIndex != rhs.orderIndex { return false } if lhs.component != rhs.component { return false } return true } } public let theme: PresentationTheme public let tabs: AnyComponent? public let panels: [Panel] public init( theme: PresentationTheme, tabs: AnyComponent?, panels: [Panel] ) { self.theme = theme self.tabs = tabs self.panels = panels } public static func ==(lhs: HeaderPanelContainerComponent, rhs: HeaderPanelContainerComponent) -> Bool { if lhs.theme !== rhs.theme { return false } if lhs.tabs != rhs.tabs { return false } if lhs.panels != rhs.panels { return false } return true } private final class PanelItemView: UIView { let view = ComponentView() let separator = SimpleLayer() override init(frame: CGRect) { super.init(frame: frame) self.clipsToBounds = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } public final class View: UIView { private let backgroundContainer: GlassBackgroundContainerView private let backgroundView: GlassBackgroundView private let contentContainer: UIView private var tabsView: ComponentView? private var panelViews: [AnyHashable: PanelItemView] = [:] private var component: HeaderPanelContainerComponent? private weak var state: EmptyComponentState? public var tabs: UIView? { return self.tabsView?.view } public func panel(forKey key: AnyHashable) -> UIView? { return self.panelViews[key]?.view.view } override init(frame: CGRect) { self.backgroundContainer = GlassBackgroundContainerView() self.backgroundView = GlassBackgroundView() self.contentContainer = UIView() super.init(frame: frame) self.backgroundContainer.contentView.addSubview(self.backgroundView) self.addSubview(self.backgroundContainer) self.contentContainer.clipsToBounds = true self.backgroundView.contentView.addSubview(self.contentContainer) } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: HeaderPanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { var isAnimatingReplacement = false if let previousComponent = self.component { isAnimatingReplacement = !component.panels.contains(where: { panel in previousComponent.panels.contains(where: { $0.key == panel.key }) }) } self.component = component self.state = state let sideInset: CGFloat = 16.0 var size = CGSize(width: availableSize.width, height: 0.0) var isFirstPanel = true if let tabs = component.tabs { let tabsView: ComponentView var tabsTransition = transition if let current = self.tabsView { tabsView = current } else { tabsTransition = tabsTransition.withAnimation(.none) tabsView = ComponentView() self.tabsView = tabsView } let tabsSize = tabsView.update( transition: tabsTransition, component: tabs, environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) ) let tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: tabsSize) if let tabsComponentView = tabsView.view { if tabsComponentView.superview == nil { self.contentContainer.addSubview(tabsComponentView) transition.animateAlpha(view: tabsComponentView, from: 0.0, to: 1.0) } tabsTransition.setFrame(view: tabsComponentView, frame: tabsFrame) } size.height += tabsSize.height isFirstPanel = false } else if let tabsView = self.tabsView { self.tabsView = nil if let tabsComponentView = tabsView.view { transition.setAlpha(view: tabsComponentView, alpha: 0.0, completion: { [weak tabsComponentView] _ in tabsComponentView?.removeFromSuperview() }) } } var validPanelKeys: [AnyHashable] = [] for panel in component.panels { validPanelKeys.append(panel.key) var panelTransition = transition let panelView: PanelItemView if let current = self.panelViews[panel.key] { panelView = current } else { panelTransition = panelTransition.withAnimation(.none) panelView = PanelItemView() self.panelViews[panel.key] = panelView self.contentContainer.layer.insertSublayer(panelView.separator, at: 0) self.contentContainer.addSubview(panelView) } let panelSize = panelView.view.update( transition: panelTransition, component: panel.component, environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) ) let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: panelSize) if let panelComponentView = panelView.view.view { if panelComponentView.superview == nil { panelView.addSubview(panelComponentView) transition.animateAlpha(view: panelView, from: 0.0, to: 1.0) panelView.separator.opacity = 0.0 if isAnimatingReplacement { panelView.frame = panelFrame } else { panelView.frame = CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: 0.0)) } } panelView.separator.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor transition.setFrame(view: panelView, frame: panelFrame) panelTransition.setFrame(view: panelComponentView, frame: CGRect(origin: CGPoint(), size: panelFrame.size)) panelTransition.setFrame(layer: panelView.separator, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel))) transition.setAlpha(layer: panelView.separator, alpha: isFirstPanel ? 0.0 : 1.0) } size.height += panelSize.height isFirstPanel = false } var removedPanelKeys: [AnyHashable] = [] for (key, panelView) in self.panelViews { if !validPanelKeys.contains(key) { removedPanelKeys.append(key) transition.setAlpha(view: panelView, alpha: 0.0, completion: { [weak panelView] _ in panelView?.removeFromSuperview() }) let separator = panelView.separator transition.setAlpha(layer: separator, alpha: 0.0, completion: { [weak separator] _ in separator?.removeFromSuperlayer() }) if !isAnimatingReplacement { transition.setFrame(view: panelView, frame: CGRect(origin: panelView.frame.origin, size: CGSize(width: panelView.bounds.width, height: 0.0))) } } } for key in removedPanelKeys { self.panelViews.removeValue(forKey: key) } transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) self.backgroundContainer.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) let backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: size.width - sideInset * 2.0, height: size.height)) transition.setFrame(view: self.backgroundView, frame: backgroundFrame) self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) self.contentContainer.layer.cornerRadius = 20.0 return size } } public func makeView() -> View { return View(frame: CGRect()) } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }