import Foundation import UIKit import Display import ComponentFlow import SwiftSignalKit import GZip import AppBundle import LegacyComponents import AvatarNode import AccountContext import TelegramCore import AnimationCache import MultiAnimationRenderer import EmojiStatusComponent class EmojiHeaderComponent: Component { let context: AccountContext let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer let placeholderColor: UIColor let accentColor: UIColor let fileId: Int64 let isVisible: Bool let hasIdleAnimations: Bool init( context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, placeholderColor: UIColor, accentColor: UIColor, fileId: Int64, isVisible: Bool, hasIdleAnimations: Bool ) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer self.placeholderColor = placeholderColor self.accentColor = accentColor self.fileId = fileId self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations } static func ==(lhs: EmojiHeaderComponent, rhs: EmojiHeaderComponent) -> Bool { return lhs.placeholderColor == rhs.placeholderColor && lhs.accentColor == rhs.accentColor && lhs.fileId == rhs.fileId && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations } final class View: UIView, ComponentTaggedView { final class Tag { } func matches(tag: Any) -> Bool { if let _ = tag as? Tag { return true } return false } private var _ready = Promise(true) var ready: Signal { return self._ready.get() } weak var animateFrom: UIView? weak var containerView: UIView? let statusView: ComponentHostView private var hasIdleAnimations = false override init(frame: CGRect) { self.statusView = ComponentHostView() super.init(frame: frame) self.statusView.isHidden = true self.addSubview(self.statusView) self.disablesInteractiveModalDismiss = true self.disablesInteractiveTransitionGestureRecognizer = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } var scheduledAnimateIn = false override func didMoveToWindow() { super.didMoveToWindow() if self.scheduledAnimateIn { self.animateIn() self.scheduledAnimateIn = false } } func animateIn() { guard let animateFrom = self.animateFrom, var containerView = self.containerView else { return } guard let _ = self.window else { self.scheduledAnimateIn = true return } self.statusView.isHidden = false if containerView.subviews.count > 1 && containerView.subviews[1].subviews.count > 1 { containerView = containerView.subviews[1].subviews[1] } let initialPosition = self.statusView.center let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView) let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: 0.0) containerView.addSubview(self.statusView) self.statusView.center = targetPosition animateFrom.alpha = 0.0 self.statusView.layer.animateScale(from: 0.24, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.linear.rawValue) self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring) Queue.mainQueue().after(0.55, { self.addSubview(self.statusView) self.statusView.center = initialPosition }) Queue.mainQueue().after(0.4, { animateFrom.alpha = 1.0 }) self.animateFrom = nil self.containerView = nil } func update(component: EmojiHeaderComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.hasIdleAnimations = component.hasIdleAnimations let size = self.statusView.update( transition: .immediate, component: AnyComponent(EmojiStatusComponent( context: component.context, animationCache: component.animationCache, animationRenderer: component.animationRenderer, content: .animation( content: .customEmoji(fileId: component.fileId), size: CGSize(width: 100.0, height: 100.0), placeholderColor: component.placeholderColor, themeColor: component.accentColor, loopMode: .forever ), isVisibleForAnimations: true, action: nil )), environment: {}, containerSize: CGSize(width: 96.0, height: 96.0) ) self.statusView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 63.0), size: size) return availableSize } } func makeView() -> View { return View(frame: CGRect()) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } } private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [CGPoint] { let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation) let x1 = sourcePoint.x let y1 = sourcePoint.y let x2 = midPoint.x let y2 = midPoint.y let x3 = targetPosition.x let y3 = targetPosition.y var keyframes: [CGPoint] = [] if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 { for i in 0 ..< 10 { let k = CGFloat(i) / CGFloat(10 - 1) let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k keyframes.append(CGPoint(x: x, y: y)) } } else { let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) for i in 0 ..< 10 { let k = CGFloat(i) / CGFloat(10 - 1) let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k let y = a * x * x + b * x + c keyframes.append(CGPoint(x: x, y: y)) } } return keyframes }