import Foundation import UIKit import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData import TelegramStringFormatting private let purple = UIColor(rgb: 0x3252ef) private let pink = UIColor(rgb: 0xef436c) private let latePurple = UIColor(rgb: 0x974aa9) private let latePink = UIColor(rgb: 0xf0436c) final class VoiceChatTimerNode: ASDisplayNode { private let strings: PresentationStrings private let dateTimeFormat: PresentationDateTimeFormat private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode private let timerNode: ImmediateTextNode private let foregroundView = UIView() private let foregroundGradientLayer = CAGradientLayer() private let maskView = UIView() private var validLayout: CGSize? private var updateTimer: SwiftSignalKit.Timer? private let hierarchyTrackingNode: HierarchyTrackingNode private var isCurrentlyInHierarchy = false private var isLate = false init(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) { self.strings = strings self.dateTimeFormat = dateTimeFormat var updateInHierarchy: ((Bool) -> Void)? self.hierarchyTrackingNode = HierarchyTrackingNode({ value in updateInHierarchy?(value) }) self.titleNode = ImmediateTextNode() self.subtitleNode = ImmediateTextNode() self.timerNode = ImmediateTextNode() super.init() self.addSubnode(self.hierarchyTrackingNode) self.allowsGroupOpacity = true self.isUserInteractionEnabled = false self.foregroundGradientLayer.type = .radial self.foregroundGradientLayer.colors = [pink.cgColor, purple.cgColor, purple.cgColor] self.foregroundGradientLayer.locations = [0.0, 0.85, 1.0] self.foregroundGradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0) self.foregroundGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) self.foregroundView.mask = self.maskView self.foregroundView.layer.addSublayer(self.foregroundGradientLayer) self.view.addSubview(self.foregroundView) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) self.maskView.addSubnode(self.timerNode) updateInHierarchy = { [weak self] value in if let strongSelf = self { strongSelf.isCurrentlyInHierarchy = value strongSelf.updateAnimations() } } } deinit { self.updateTimer?.invalidate() } func animateIn() { self.foregroundView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, damping: 100.0) } private func updateAnimations() { if self.isInHierarchy { self.setupGradientAnimations() } else { self.foregroundGradientLayer.removeAllAnimations() } } private func setupGradientAnimations() { if let _ = self.foregroundGradientLayer.animation(forKey: "movement") { } else { let previousValue = self.foregroundGradientLayer.startPoint let newValue = CGPoint(x: CGFloat.random(in: 0.65 ..< 0.85), y: CGFloat.random(in: 0.1 ..< 0.45)) self.foregroundGradientLayer.startPoint = newValue CATransaction.begin() let animation = CABasicAnimation(keyPath: "startPoint") animation.duration = Double.random(in: 0.8 ..< 1.4) animation.fromValue = previousValue animation.toValue = newValue CATransaction.setCompletionBlock { [weak self] in if let isCurrentlyInHierarchy = self?.isCurrentlyInHierarchy, isCurrentlyInHierarchy { self?.setupGradientAnimations() } } self.foregroundGradientLayer.add(animation, forKey: "movement") CATransaction.commit() } } func update(size: CGSize, participants: Int32, groupingSeparator: String, transition: ContainedViewLayoutTransition) { if self.validLayout == nil { self.updateAnimations() } self.validLayout = size self.foregroundView.frame = CGRect(origin: CGPoint(), size: size) self.foregroundGradientLayer.frame = self.foregroundView.bounds self.maskView.frame = self.foregroundView.bounds let text: String = presentationStringsFormattedNumber(participants, groupingSeparator) let subtitle = "listening" self.titleNode.attributedText = NSAttributedString(string: "", font: Font.with(size: 23.0, design: .round, weight: .semibold, traits: []), textColor: .white) let titleSize = self.titleNode.updateLayout(size) self.titleNode.frame = CGRect(x: floor((size.width - titleSize.width) / 2.0), y: 48.0, width: titleSize.width, height: titleSize.height) self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white) var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height)) if timerSize.width > size.width - 32.0 { self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white) timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height)) } self.timerNode.frame = CGRect(x: floor((size.width - timerSize.width) / 2.0), y: 78.0, width: timerSize.width, height: timerSize.height) self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.with(size: 21.0, design: .round, weight: .semibold, traits: []), textColor: .white) let subtitleSize = self.subtitleNode.updateLayout(size) self.subtitleNode.frame = CGRect(x: floor((size.width - subtitleSize.width) / 2.0), y: 164.0, width: subtitleSize.width, height: subtitleSize.height) self.foregroundView.frame = CGRect(origin: CGPoint(), size: size) } func update(size: CGSize, scheduleTime: Int32?, transition: ContainedViewLayoutTransition) { if self.validLayout == nil { self.updateAnimations() } self.validLayout = size guard let scheduleTime = scheduleTime else { return } self.foregroundView.frame = CGRect(origin: CGPoint(), size: size) self.foregroundGradientLayer.frame = self.foregroundView.bounds self.maskView.frame = self.foregroundView.bounds let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let elapsedTime = scheduleTime - currentTime let timerText: String if elapsedTime >= 86400 { timerText = scheduledTimeIntervalString(strings: self.strings, value: elapsedTime) } else { timerText = textForTimeout(value: abs(elapsedTime)) if elapsedTime < 0 && !self.isLate { self.isLate = true self.foregroundGradientLayer.colors = [latePink.cgColor, latePurple.cgColor, latePurple.cgColor] } } if self.updateTimer == nil { let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in if let strongSelf = self, let size = strongSelf.validLayout { strongSelf.update(size: size, scheduleTime: scheduleTime, transition: .immediate) } }, queue: Queue.mainQueue()) self.updateTimer = timer timer.start() } let subtitle = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: scheduleTime, alwaysShowTime: true).string self.titleNode.attributedText = NSAttributedString(string: elapsedTime < 0 ? self.strings.VoiceChat_LateBy : self.strings.VoiceChat_StartsIn, font: Font.with(size: 23.0, design: .round, weight: .semibold, traits: []), textColor: .white) let titleSize = self.titleNode.updateLayout(size) self.titleNode.frame = CGRect(x: floor((size.width - titleSize.width) / 2.0), y: 48.0, width: titleSize.width, height: titleSize.height) self.timerNode.attributedText = NSAttributedString(string: timerText, font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white) var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height)) if timerSize.width > size.width - 32.0 { self.timerNode.attributedText = NSAttributedString(string: timerText, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white) timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height)) } self.timerNode.frame = CGRect(x: floor((size.width - timerSize.width) / 2.0), y: 78.0, width: timerSize.width, height: timerSize.height) self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.with(size: 21.0, design: .round, weight: .semibold, traits: []), textColor: .white) let subtitleSize = self.subtitleNode.updateLayout(size) self.subtitleNode.frame = CGRect(x: floor((size.width - subtitleSize.width) / 2.0), y: 164.0, width: subtitleSize.width, height: subtitleSize.height) self.foregroundView.frame = CGRect(origin: CGPoint(), size: size) } }