mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
173 lines
7.4 KiB
Swift
173 lines
7.4 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import AppBundle
|
|
|
|
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: 0x007ee5))
|
|
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007ee5, 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.value(forKeyPath: "transform.scale") as? AnyObject, currentScale.responds(to: Selector("floatValue")) {
|
|
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
|
|
}
|
|
}
|