import Foundation import UIKit import Display import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore import Postbox final class EntityKeyboardTopContainerPanelEnvironment: Equatable { let visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)> init( visibilityFractionUpdated: ActionSlot<(CGFloat, Transition)> ) { self.visibilityFractionUpdated = visibilityFractionUpdated } static func ==(lhs: EntityKeyboardTopContainerPanelEnvironment, rhs: EntityKeyboardTopContainerPanelEnvironment) -> Bool { if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated { return false } return true } } final class EntityKeyboardTopContainerPanelComponent: Component { typealias EnvironmentType = PagerComponentPanelEnvironment let theme: PresentationTheme init( theme: PresentationTheme ) { self.theme = theme } static func ==(lhs: EntityKeyboardTopContainerPanelComponent, rhs: EntityKeyboardTopContainerPanelComponent) -> Bool { if lhs.theme !== rhs.theme { return false } return true } private final class PanelView { let view = ComponentHostView() let visibilityFractionUpdated = ActionSlot<(CGFloat, Transition)>() } final class View: UIView { private var panelViews: [AnyHashable: PanelView] = [:] private var component: EntityKeyboardTopContainerPanelComponent? private var panelEnvironment: PagerComponentPanelEnvironment? private weak var state: EmptyComponentState? private var visibilityFraction: CGFloat = 1.0 override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { let intrinsicHeight: CGFloat = 41.0 let height = intrinsicHeight let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value var transitionOffsetFraction: CGFloat = 0.0 if case .none = transition.animation { } else if let previousPanelEnvironment = self.panelEnvironment, let previousActiveContentId = previousPanelEnvironment.activeContentId, let activeContentId = panelEnvironment.activeContentId, previousActiveContentId != activeContentId { if let previousIndex = panelEnvironment.contentTopPanels.firstIndex(where: { $0.id == previousActiveContentId }), let index = panelEnvironment.contentTopPanels.firstIndex(where: { $0.id == activeContentId }), previousIndex != index { if index < previousIndex { transitionOffsetFraction = -1.0 } else { transitionOffsetFraction = 1.0 } } } self.component = component self.panelEnvironment = panelEnvironment self.state = state var validPanelIds = Set() let visibleBounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: intrinsicHeight)) if let centralId = panelEnvironment.activeContentId, let centralIndex = panelEnvironment.contentTopPanels.firstIndex(where: { $0.id == centralId }) { for index in 0 ..< panelEnvironment.contentTopPanels.count { let panel = panelEnvironment.contentTopPanels[index] let indexOffset = index - centralIndex let panelFrame = CGRect(origin: CGPoint(x: CGFloat(indexOffset) * availableSize.width, y: 0.0), size: CGSize(width: availableSize.width, height: intrinsicHeight)) let isInBounds = visibleBounds.intersects(panelFrame) let isPartOfTransition: Bool if !transitionOffsetFraction.isZero && self.panelViews[panel.id] != nil { isPartOfTransition = true } else { isPartOfTransition = false } if isInBounds || isPartOfTransition { validPanelIds.insert(panel.id) var panelTransition = transition let panelView: PanelView if let current = self.panelViews[panel.id] { panelView = current } else { panelTransition = .immediate panelView = PanelView() self.panelViews[panel.id] = panelView self.addSubview(panelView.view) } let _ = panelView.view.update( transition: panelTransition, component: panel.component, environment: { EntityKeyboardTopContainerPanelEnvironment( visibilityFractionUpdated: panelView.visibilityFractionUpdated ) }, containerSize: panelFrame.size ) if isInBounds { transition.animatePosition(view: panelView.view, from: CGPoint(x: transitionOffsetFraction * availableSize.width, y: 0.0), to: CGPoint(), additive: true, completion: nil) } panelTransition.setFrame(view: panelView.view, frame: panelFrame, completion: { [weak self] completed in if isPartOfTransition && completed { self?.state?.updated(transition: .immediate) } }) } } } var removedPanelIds: [AnyHashable] = [] for (id, panelView) in self.panelViews { if !validPanelIds.contains(id) { removedPanelIds.append(id) panelView.view.removeFromSuperview() } } for id in removedPanelIds { self.panelViews.removeValue(forKey: id) } environment[PagerComponentPanelEnvironment.self].value.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in guard let strongSelf = self else { return } strongSelf.updateVisibilityFraction(value: fraction, transition: transition) } return CGSize(width: availableSize.width, height: height) } private func updateVisibilityFraction(value: CGFloat, transition: Transition) { if self.visibilityFraction == value { return } self.visibilityFraction = value for (_, panelView) in self.panelViews { panelView.visibilityFractionUpdated.invoke((value, transition)) transition.setSublayerTransform(view: panelView.view, transform: CATransform3DMakeTranslation(0.0, -panelView.view.bounds.height / 2.0 * (1.0 - value), 0.0)) } } } func makeView() -> View { return View(frame: CGRect()) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }