Swiftgram/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingOverlayButton.swift
2021-10-06 00:06:33 +04:00

174 lines
7.4 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import AppBundle
import ObjCRuntimeUtils
private let innerCircleDiameter: CGFloat = 110.0
private let outerCircleDiameter = innerCircleDiameter + 50.0
private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x007aff))
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007aff, alpha: 0.2))
private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white)!
private final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject {
private let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
super.init()
}
@objc func displayLinkEvent() {
self.f()
}
}
final class ChatTextInputAudioRecordingOverlay {
private weak var anchorView: UIView?
private let containerNode: ASDisplayNode
private let circleContainerNode: ASDisplayNode
private let innerCircleNode: ASImageNode
private let outerCircleNode: ASImageNode
private let iconNode: ASImageNode
var animationStartTime: Double?
var displayLink: CADisplayLink?
var currentLevel: CGFloat = 0.0
var inputLevel: CGFloat = 0.0
var animatedIn = false
var dismissFactor: CGFloat = 1.0 {
didSet {
let scale = max(0.3, min(self.dismissFactor, 1.0))
self.circleContainerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
}
}
init(anchorView: UIView) {
self.anchorView = anchorView
self.containerNode = ASDisplayNode()
self.containerNode.isLayerBacked = true
self.circleContainerNode = ASDisplayNode()
self.circleContainerNode.isLayerBacked = true
self.outerCircleNode = ASImageNode()
self.outerCircleNode.displayWithoutProcessing = true
self.outerCircleNode.displaysAsynchronously = false
self.outerCircleNode.isLayerBacked = true
self.outerCircleNode.image = outerCircleImage
self.outerCircleNode.frame = CGRect(origin: CGPoint(x: -outerCircleDiameter / 2.0, y: -outerCircleDiameter / 2.0), size: CGSize(width: outerCircleDiameter, height: outerCircleDiameter))
self.innerCircleNode = ASImageNode()
self.innerCircleNode.displayWithoutProcessing = true
self.innerCircleNode.displaysAsynchronously = false
self.innerCircleNode.isLayerBacked = true
self.innerCircleNode.image = innerCircleImage
self.innerCircleNode.frame = CGRect(origin: CGPoint(x: -innerCircleDiameter / 2.0, y: -innerCircleDiameter / 2.0), size: CGSize(width: innerCircleDiameter, height: innerCircleDiameter))
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.isLayerBacked = true
self.iconNode.image = micIcon
self.iconNode.frame = CGRect(origin: CGPoint(x: -micIcon.size.width / 2.0, y: -micIcon.size.height / 2.0), size: micIcon.size)
self.circleContainerNode.addSubnode(self.outerCircleNode)
self.circleContainerNode.addSubnode(self.innerCircleNode)
self.containerNode.addSubnode(self.circleContainerNode)
self.containerNode.addSubnode(self.iconNode)
}
deinit {
self.displayLink?.invalidate()
}
func present(in window: UIWindow) {
if let anchorView = self.anchorView, let anchorSuperview = anchorView.superview {
if let displayLink = self.displayLink {
displayLink.invalidate()
}
self.displayLink = CADisplayLink(target: ChatTextInputAudioRecordingOverlayDisplayLinkTarget({ [weak self] in
self?.displayLinkEvent()
}), selector: #selector(ChatTextInputAudioRecordingOverlayDisplayLinkTarget.displayLinkEvent))
let convertedCenter = anchorSuperview.convert(anchorView.center, to: window)
self.containerNode.position = CGPoint(x: convertedCenter.x, y: convertedCenter.y)
window.addSubnode(self.containerNode)
self.innerCircleNode.layer.animateSpring(from: 0.2 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
self.outerCircleNode.layer.transform = CATransform3DMakeScale(outerCircleMinScale, outerCircleMinScale, 1.0)
self.outerCircleNode.layer.animateSpring(from: 0.2 as NSNumber, to: outerCircleMinScale as NSNumber, keyPath: "transform.scale", duration: 0.5)
self.innerCircleNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.outerCircleNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.iconNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.animatedIn = true
self.animationStartTime = CACurrentMediaTime()
self.displayLink?.add(to: RunLoop.main, forMode: .common)
self.displayLink?.isPaused = false
}
}
func dismiss() {
self.displayLink?.invalidate()
self.displayLink = nil
var innerCompleted = false
var outerCompleted = false
var iconCompleted = false
var containerNodeRef: ASDisplayNode? = self.containerNode
let completion: () -> Void = {
if let containerNode = containerNodeRef, innerCompleted, outerCompleted, iconCompleted {
containerNode.removeFromSupernode()
containerNodeRef = nil
}
}
self.innerCircleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
self.innerCircleNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.18, removeOnCompletion: false, completion: { _ in
innerCompleted = true
completion()
})
var currentScaleValue: CGFloat = outerCircleMinScale
if let currentScale = self.outerCircleNode.layer.floatValue(forKeyPath: "transform.scale") {
currentScaleValue = CGFloat(currentScale.floatValue)
}
self.outerCircleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
self.outerCircleNode.layer.animateScale(from: currentScaleValue, to: 0.2, duration: 0.18, removeOnCompletion: false, completion: { _ in
outerCompleted = true
completion()
})
self.iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { _ in
iconCompleted = true
completion()
})
}
private func displayLinkEvent() {
let t = CACurrentMediaTime()
if let animationStartTime = self.animationStartTime {
if t > animationStartTime + 0.5 {
self.currentLevel = self.currentLevel * 0.8 + self.inputLevel * 0.2
let scale = outerCircleMinScale + self.currentLevel * (1.0 - outerCircleMinScale)
self.outerCircleNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
}
}
}
func addImmediateMicLevel(_ level: CGFloat) {
self.inputLevel = level
}
}