mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Voice Chat UI improvements
This commit is contained in:
parent
b900f159cf
commit
572bc95254
@ -9,18 +9,28 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
|
||||||
|
private class CurveDrawingState: NSObject {
|
||||||
|
let path: UIBezierPath
|
||||||
|
let offset: CGFloat
|
||||||
|
let alpha: CGFloat
|
||||||
|
|
||||||
|
init(path: UIBezierPath, offset: CGFloat, alpha: CGFloat) {
|
||||||
|
self.path = path
|
||||||
|
self.offset = offset
|
||||||
|
self.alpha = alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class CallStatusBarBackgroundNodeDrawingState: NSObject {
|
private class CallStatusBarBackgroundNodeDrawingState: NSObject {
|
||||||
let timestamp: Double
|
let timestamp: Double
|
||||||
let amplitude: CGFloat
|
let curves: [CurveDrawingState]
|
||||||
let phase: CGFloat
|
|
||||||
let speaking: Bool
|
let speaking: Bool
|
||||||
let gradientTransition: CGFloat
|
let gradientTransition: CGFloat
|
||||||
let gradientMovement: CGFloat
|
let gradientMovement: CGFloat
|
||||||
|
|
||||||
init(timestamp: Double, amplitude: CGFloat, phase: CGFloat, speaking: Bool, gradientTransition: CGFloat, gradientMovement: CGFloat) {
|
init(timestamp: Double, curves: [CurveDrawingState], speaking: Bool, gradientTransition: CGFloat, gradientMovement: CGFloat) {
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.amplitude = amplitude
|
self.curves = curves
|
||||||
self.phase = phase
|
|
||||||
self.speaking = speaking
|
self.speaking = speaking
|
||||||
self.gradientTransition = gradientTransition
|
self.gradientTransition = gradientTransition
|
||||||
self.gradientMovement = gradientMovement
|
self.gradientMovement = gradientMovement
|
||||||
@ -37,11 +47,11 @@ private final class Curve {
|
|||||||
let minSpeed: CGFloat
|
let minSpeed: CGFloat
|
||||||
let maxSpeed: CGFloat
|
let maxSpeed: CGFloat
|
||||||
|
|
||||||
let size: CGSize
|
var size: CGSize
|
||||||
|
let alpha: CGFloat
|
||||||
var currentOffset: CGFloat = 1.0
|
var currentOffset: CGFloat = 1.0
|
||||||
var minOffset: CGFloat = 0.0
|
var minOffset: CGFloat = 0.0
|
||||||
var maxOffset: CGFloat = 2.0
|
var maxOffset: CGFloat = 2.0
|
||||||
let scaleSpeed: CGFloat
|
|
||||||
|
|
||||||
private var speedLevel: CGFloat = 0.0
|
private var speedLevel: CGFloat = 0.0
|
||||||
private var lastSpeedLevel: CGFloat = 0.0
|
private var lastSpeedLevel: CGFloat = 0.0
|
||||||
@ -62,14 +72,14 @@ private final class Curve {
|
|||||||
private var transition: CGFloat = 0 {
|
private var transition: CGFloat = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
if let currentPoints = self.currentPoints {
|
if let currentPoints = self.currentPoints {
|
||||||
self.currentShape = UIBezierPath.smoothCurve(through: currentPoints, length: size.width, smoothness: smoothness)
|
self.currentShape = UIBezierPath.smoothCurve(through: currentPoints, length: size.width, smoothness: smoothness, curve: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var level: CGFloat = 0.0 {
|
var level: CGFloat = 0.0 {
|
||||||
didSet {
|
didSet {
|
||||||
self.currentOffset = self.minOffset + (self.maxOffset - self.minOffset) * self.level
|
self.currentOffset = min(self.maxOffset, max(self.minOffset, self.minOffset + (self.maxOffset - self.minOffset) * self.level))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,17 +96,17 @@ private final class Curve {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
size: CGSize,
|
size: CGSize,
|
||||||
|
alpha: CGFloat,
|
||||||
pointsCount: Int,
|
pointsCount: Int,
|
||||||
minRandomness: CGFloat,
|
minRandomness: CGFloat,
|
||||||
maxRandomness: CGFloat,
|
maxRandomness: CGFloat,
|
||||||
minSpeed: CGFloat,
|
minSpeed: CGFloat,
|
||||||
maxSpeed: CGFloat,
|
maxSpeed: CGFloat,
|
||||||
minOffset: CGFloat,
|
minOffset: CGFloat,
|
||||||
maxOffset: CGFloat,
|
maxOffset: CGFloat
|
||||||
scaleSpeed: CGFloat
|
|
||||||
) {
|
) {
|
||||||
self.size = size
|
self.size = size
|
||||||
// self.alpha = alpha
|
self.alpha = alpha
|
||||||
self.pointsCount = pointsCount
|
self.pointsCount = pointsCount
|
||||||
self.minRandomness = minRandomness
|
self.minRandomness = minRandomness
|
||||||
self.maxRandomness = maxRandomness
|
self.maxRandomness = maxRandomness
|
||||||
@ -104,7 +114,6 @@ private final class Curve {
|
|||||||
self.maxSpeed = maxSpeed
|
self.maxSpeed = maxSpeed
|
||||||
self.minOffset = minOffset
|
self.minOffset = minOffset
|
||||||
self.maxOffset = maxOffset
|
self.maxOffset = maxOffset
|
||||||
self.scaleSpeed = scaleSpeed
|
|
||||||
|
|
||||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
||||||
@ -147,13 +156,11 @@ private final class Curve {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateAnimations() {
|
func updateAnimations() {
|
||||||
var animate = false
|
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
|
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
|
||||||
self.transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
self.transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
||||||
if self.transition < 1.0 {
|
if self.transition < 1.0 {
|
||||||
animate = true
|
|
||||||
} else {
|
} else {
|
||||||
if self.loop {
|
if self.loop {
|
||||||
self.animateToNewShape()
|
self.animateToNewShape()
|
||||||
@ -169,13 +176,13 @@ private final class Curve {
|
|||||||
|
|
||||||
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
||||||
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
||||||
return blob(pointsCount: pointsCount, randomness: randomness).map {
|
return curve(pointsCount: pointsCount, randomness: randomness).map {
|
||||||
return CGPoint(x: size.width / 2.0 + $0.x * CGFloat(size.width), y: size.height / 2.0 + $0.y * CGFloat(size.height))
|
return CGPoint(x: $0.x * CGFloat(size.width), y: size.height - 14.0 + $0.y * 12.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func blob(pointsCount: Int, randomness: CGFloat) -> [CGPoint] {
|
private func curve(pointsCount: Int, randomness: CGFloat) -> [CGPoint] {
|
||||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
let segment = 1.0 / CGFloat(pointsCount)
|
||||||
|
|
||||||
let rgen = { () -> CGFloat in
|
let rgen = { () -> CGFloat in
|
||||||
let accuracy: UInt32 = 1000
|
let accuracy: UInt32 = 1000
|
||||||
@ -184,15 +191,27 @@ private final class Curve {
|
|||||||
}
|
}
|
||||||
let rangeStart: CGFloat = 1.0 / (1.0 + randomness / 10.0)
|
let rangeStart: CGFloat = 1.0 / (1.0 + randomness / 10.0)
|
||||||
|
|
||||||
let startAngle = angle * CGFloat(arc4random_uniform(100)) / CGFloat(100)
|
|
||||||
|
|
||||||
let points = (0 ..< pointsCount).map { i -> CGPoint in
|
let points = (0 ..< pointsCount).map { i -> CGPoint in
|
||||||
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
||||||
let angleRandomness: CGFloat = angle * 0.1
|
let segmentRandomness: CGFloat = randomness
|
||||||
let randAngle = angle + angle * ((angleRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - angleRandomness * 0.5)
|
|
||||||
let pointX = sin(startAngle + CGFloat(i) * randAngle)
|
let pointX: CGFloat
|
||||||
let pointY = cos(startAngle + CGFloat(i) * randAngle)
|
let pointY: CGFloat
|
||||||
return CGPoint(x: pointX * randPointOffset, y: pointY * randPointOffset)
|
let randomXDelta: CGFloat
|
||||||
|
if i == 0 {
|
||||||
|
pointX = 0.0
|
||||||
|
pointY = 0.0
|
||||||
|
randomXDelta = 0.0
|
||||||
|
} else if i == pointsCount - 1 {
|
||||||
|
pointX = 1.0
|
||||||
|
pointY = 0.0
|
||||||
|
randomXDelta = 0.0
|
||||||
|
} else {
|
||||||
|
pointX = segment * CGFloat(i)
|
||||||
|
pointY = cos(segment * CGFloat(i)) * ((segmentRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - segmentRandomness * 0.5) * randPointOffset
|
||||||
|
randomXDelta = segment - segment * randPointOffset
|
||||||
|
}
|
||||||
|
return CGPoint(x: pointX + randomXDelta, y: pointY)
|
||||||
}
|
}
|
||||||
|
|
||||||
return points
|
return points
|
||||||
@ -202,9 +221,18 @@ private final class Curve {
|
|||||||
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||||
var muted = true
|
var muted = true
|
||||||
|
|
||||||
var audioLevel: Float = 0.0
|
var audioLevel: Float = 0.0 {
|
||||||
|
didSet {
|
||||||
|
for curve in self.curves {
|
||||||
|
curve.loop = audioLevel.isZero
|
||||||
|
curve.updateSpeedLevel(to: CGFloat(self.audioLevel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var presentationAudioLevel: CGFloat = 0.0
|
var presentationAudioLevel: CGFloat = 0.0
|
||||||
var phase: CGFloat = 0.0
|
|
||||||
|
typealias CurveRange = (min: CGFloat, max: CGFloat)
|
||||||
|
let curves: [Curve]
|
||||||
|
|
||||||
private var gradientMovementArguments: (from: CGFloat, to: CGFloat, startTime: Double, duration: Double)?
|
private var gradientMovementArguments: (from: CGFloat, to: CGFloat, startTime: Double, duration: Double)?
|
||||||
private var gradientMovement: CGFloat = 0.0
|
private var gradientMovement: CGFloat = 0.0
|
||||||
@ -221,8 +249,19 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
private var animator: ConstantDisplayLinkAnimator?
|
private var animator: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
let smallCurveRange: CurveRange = (0.0, 0.0)
|
||||||
|
let mediumCurveRange: CurveRange = (0.1, 0.55)
|
||||||
|
let bigCurveRange: CurveRange = (0.1, 1.0)
|
||||||
|
|
||||||
|
let size = CGSize(width: 375.0, height: 44.0)
|
||||||
|
let smallCurve = Curve(size: size, alpha: 1.0, pointsCount: 10, minRandomness: 1, maxRandomness: 1, minSpeed: 1.5, maxSpeed: 7, minOffset: smallCurveRange.min, maxOffset: smallCurveRange.max)
|
||||||
|
let mediumCurve = Curve(size: size, alpha: 0.55, pointsCount: 10, minRandomness: 1, maxRandomness: 2, minSpeed: 1.5, maxSpeed: 7, minOffset: mediumCurveRange.min, maxOffset: mediumCurveRange.max)
|
||||||
|
let largeCurve = Curve(size: size, alpha: 0.35, pointsCount: 10, minRandomness: 1, maxRandomness: 2, minSpeed: 1.5, maxSpeed: 7, minOffset: bigCurveRange.min, maxOffset: bigCurveRange.max)
|
||||||
|
|
||||||
|
self.curves = [smallCurve, mediumCurve, largeCurve]
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
|
|
||||||
self.updateAnimations()
|
self.updateAnimations()
|
||||||
@ -230,6 +269,9 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
func updateAnimations() {
|
func updateAnimations() {
|
||||||
self.presentationAudioLevel = self.presentationAudioLevel * 0.9 + max(0.1, CGFloat(self.audioLevel)) * 0.1
|
self.presentationAudioLevel = self.presentationAudioLevel * 0.9 + max(0.1, CGFloat(self.audioLevel)) * 0.1
|
||||||
|
for curve in self.curves {
|
||||||
|
curve.level = self.presentationAudioLevel
|
||||||
|
}
|
||||||
|
|
||||||
if self.gradientMovementArguments == nil {
|
if self.gradientMovementArguments == nil {
|
||||||
self.gradientMovementArguments = (0.0, 0.7, CACurrentMediaTime(), 1.0)
|
self.gradientMovementArguments = (0.0, 0.7, CACurrentMediaTime(), 1.0)
|
||||||
@ -266,10 +308,21 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
animator.isPaused = false
|
animator.isPaused = false
|
||||||
|
|
||||||
self.phase -= 0.05
|
for curve in self.curves {
|
||||||
|
curve.updateAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
self.setNeedsDisplay()
|
self.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var frame: CGRect {
|
||||||
|
didSet {
|
||||||
|
for curve in self.curves {
|
||||||
|
curve.size = self.frame.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
@ -282,7 +335,14 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallStatusBarBackgroundNodeDrawingState(timestamp: timestamp, amplitude: self.presentationAudioLevel, phase: self.phase, speaking: self.speaking, gradientTransition: gradientTransition, gradientMovement: self.gradientMovement)
|
var curves: [CurveDrawingState] = []
|
||||||
|
for curve in self.curves {
|
||||||
|
if let path = curve.currentShape?.copy() as? UIBezierPath {
|
||||||
|
curves.append(CurveDrawingState(path: path, offset: curve.currentOffset, alpha: curve.alpha))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallStatusBarBackgroundNodeDrawingState(timestamp: timestamp, curves: curves, speaking: self.speaking, gradientTransition: gradientTransition, gradientMovement: self.gradientMovement)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
@ -298,6 +358,8 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 1.0]
|
var locations: [CGFloat] = [0.0, 1.0]
|
||||||
let leftColor = UIColor(rgb: 0x007fff).interpolateTo(UIColor(rgb: 0x2bb76b), fraction: parameters.gradientTransition)!
|
let leftColor = UIColor(rgb: 0x007fff).interpolateTo(UIColor(rgb: 0x2bb76b), fraction: parameters.gradientTransition)!
|
||||||
let rightColor = UIColor(rgb: 0x00afff).interpolateTo(UIColor(rgb: 0x007fff), fraction: parameters.gradientTransition)!
|
let rightColor = UIColor(rgb: 0x00afff).interpolateTo(UIColor(rgb: 0x007fff), fraction: parameters.gradientTransition)!
|
||||||
@ -306,54 +368,24 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
let position: CGFloat = bounds.height - 6.0
|
var i = 0
|
||||||
let maxAmplitude: CGFloat = 12.0
|
for curve in parameters.curves.reversed() {
|
||||||
|
context.saveGState()
|
||||||
let amplitude = max(0.35, parameters.amplitude)
|
|
||||||
|
|
||||||
func drawWave(_ index: Int, maxAmplitude: CGFloat, normalizedAmplitude: CGFloat) {
|
|
||||||
let path = UIBezierPath()
|
|
||||||
let mid = bounds.width / 2.0
|
|
||||||
|
|
||||||
var offset: CGFloat = 0.0
|
let path = curve.path
|
||||||
if index > 0 {
|
if i < 2 {
|
||||||
offset = 3.0 * parameters.amplitude * CGFloat(index)
|
let transform = CGAffineTransform(translationX: 0.0, y: min(1.0, curve.offset) * 16.0)
|
||||||
|
path.apply(transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
let frequency: CGFloat = 3.5
|
|
||||||
let density: CGFloat = 2.0
|
|
||||||
for x in stride(from: 0.0, to: bounds.width + density, by: density) {
|
|
||||||
let scaling = -pow(1 / mid * (x - mid), 2) + 1
|
|
||||||
let y = scaling * maxAmplitude * normalizedAmplitude * sin(CGFloat(2 * Double.pi) * frequency * (x / bounds.width) + parameters.phase) + position + offset
|
|
||||||
if x == 0 {
|
|
||||||
path.move(to: CGPoint())
|
|
||||||
}
|
|
||||||
path.addLine(to: CGPoint(x: x, y: y))
|
|
||||||
}
|
|
||||||
path.addLine(to: CGPoint(x: bounds.width, y: 0.0))
|
|
||||||
path.close()
|
|
||||||
|
|
||||||
context.addPath(path.cgPath)
|
context.addPath(path.cgPath)
|
||||||
context.clip()
|
context.clip()
|
||||||
}
|
|
||||||
|
|
||||||
for i in (0 ..< 3).reversed() {
|
|
||||||
let progress = 1.0 - CGFloat(i) / 3.0
|
|
||||||
var normalizedAmplitude = (1.5 * progress - 0.8) * amplitude
|
|
||||||
if i == 1 {
|
|
||||||
normalizedAmplitude *= -1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
context.saveGState()
|
|
||||||
drawWave(i, maxAmplitude: maxAmplitude, normalizedAmplitude: normalizedAmplitude)
|
|
||||||
|
|
||||||
if i == 1 {
|
context.setAlpha(curve.alpha)
|
||||||
context.setFillColor(UIColor(rgb: 0x007fff, alpha: 0.3).cgColor)
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: bounds.width + parameters.gradientMovement * bounds.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||||
context.fill(bounds)
|
|
||||||
} else {
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: bounds.width + parameters.gradientMovement * bounds.width, y: 0.0), options: CGGradientDrawingOptions())
|
|
||||||
}
|
|
||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,16 +489,20 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
strongSelf.update()
|
strongSelf.update()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
self.audioLevelDisposable.set((call.myAudioLevel
|
self.audioLevelDisposable.set((combineLatest(call.myAudioLevel, .single([]) |> then(call.audioLevels))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] level in
|
|> deliverOnMainQueue).start(next: { [weak self] myAudioLevel, audioLevels in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var effectiveLevel: Float = 0.0
|
var effectiveLevel: Float = 0.0
|
||||||
|
let maxLevel: Float = 3.0
|
||||||
if !strongSelf.currentIsMuted {
|
if !strongSelf.currentIsMuted {
|
||||||
effectiveLevel = level
|
effectiveLevel = min(1.0, max(myAudioLevel / maxLevel, 0))
|
||||||
|
} else {
|
||||||
|
let level = audioLevels.map { $0.1 }.max() ?? 0.0
|
||||||
|
effectiveLevel = min(1.0, max(level / maxLevel, 0))
|
||||||
}
|
}
|
||||||
strongSelf.backgroundNode.audioLevel = max(0.0, min(1.0, effectiveLevel / 8.0))
|
strongSelf.backgroundNode.audioLevel = effectiveLevel
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
self.didSetupData = true
|
self.didSetupData = true
|
||||||
@ -509,6 +545,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
self.subtitleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)
|
self.subtitleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)
|
||||||
|
|
||||||
self.backgroundNode.speaking = !self.currentIsMuted
|
self.backgroundNode.speaking = !self.currentIsMuted
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 7.0))
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 14.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,6 @@ private final class Blob {
|
|||||||
var currentScale: CGFloat = 1.0
|
var currentScale: CGFloat = 1.0
|
||||||
var minScale: CGFloat
|
var minScale: CGFloat
|
||||||
var maxScale: CGFloat
|
var maxScale: CGFloat
|
||||||
let scaleSpeed: CGFloat
|
|
||||||
|
|
||||||
private var speedLevel: CGFloat = 0.0
|
private var speedLevel: CGFloat = 0.0
|
||||||
private var lastSpeedLevel: CGFloat = 0.0
|
private var lastSpeedLevel: CGFloat = 0.0
|
||||||
@ -184,8 +183,7 @@ private final class Blob {
|
|||||||
minSpeed: CGFloat,
|
minSpeed: CGFloat,
|
||||||
maxSpeed: CGFloat,
|
maxSpeed: CGFloat,
|
||||||
minScale: CGFloat,
|
minScale: CGFloat,
|
||||||
maxScale: CGFloat,
|
maxScale: CGFloat
|
||||||
scaleSpeed: CGFloat
|
|
||||||
) {
|
) {
|
||||||
self.size = size
|
self.size = size
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
@ -196,7 +194,6 @@ private final class Blob {
|
|||||||
self.maxSpeed = maxSpeed
|
self.maxSpeed = maxSpeed
|
||||||
self.minScale = minScale
|
self.minScale = minScale
|
||||||
self.maxScale = maxScale
|
self.maxScale = maxScale
|
||||||
self.scaleSpeed = scaleSpeed
|
|
||||||
|
|
||||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
||||||
@ -320,8 +317,8 @@ private final class VoiceChatActionButtonBackgroundNodeBlobContext: VoiceChatAct
|
|||||||
let mediumBlobRange: BlobRange = (0.69, 0.87)
|
let mediumBlobRange: BlobRange = (0.69, 0.87)
|
||||||
let bigBlobRange: BlobRange = (0.71, 1.00)
|
let bigBlobRange: BlobRange = (0.71, 1.00)
|
||||||
|
|
||||||
let mediumBlob = Blob(size: size, alpha: 0.55, pointsCount: 8, minRandomness: 1, maxRandomness: 1, minSpeed: 1.5, maxSpeed: 7, minScale: mediumBlobRange.min, maxScale: mediumBlobRange.max, scaleSpeed: 0.2)
|
let mediumBlob = Blob(size: size, alpha: 0.55, pointsCount: 8, minRandomness: 1, maxRandomness: 1, minSpeed: 1.5, maxSpeed: 7, minScale: mediumBlobRange.min, maxScale: mediumBlobRange.max)
|
||||||
let largeBlob = Blob(size: size, alpha: 0.35, pointsCount: 8, minRandomness: 1, maxRandomness: 1, minSpeed: 1.5, maxSpeed: 7, minScale: bigBlobRange.min, maxScale: bigBlobRange.max, scaleSpeed: 0.2)
|
let largeBlob = Blob(size: size, alpha: 0.35, pointsCount: 8, minRandomness: 1, maxRandomness: 1, minSpeed: 1.5, maxSpeed: 7, minScale: bigBlobRange.min, maxScale: bigBlobRange.max)
|
||||||
|
|
||||||
self.blobs = [largeBlob, mediumBlob]
|
self.blobs = [largeBlob, mediumBlob]
|
||||||
}
|
}
|
||||||
@ -502,6 +499,21 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.updateAnimations()
|
self.updateAnimations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isCurrentlyInHierarchy = false
|
||||||
|
override func didEnterHierarchy() {
|
||||||
|
super.didEnterHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = true
|
||||||
|
self.updateAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didExitHierarchy() {
|
||||||
|
super.didExitHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = false
|
||||||
|
self.updateAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
private func updateAnimations() {
|
private func updateAnimations() {
|
||||||
var animate = false
|
var animate = false
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
@ -549,6 +561,10 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.state.updateAnimations()
|
self.state.updateAnimations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.isCurrentlyInHierarchy {
|
||||||
|
animate = false
|
||||||
|
}
|
||||||
|
|
||||||
if animate {
|
if animate {
|
||||||
let animator: ConstantDisplayLinkAnimator
|
let animator: ConstantDisplayLinkAnimator
|
||||||
if let current = self.animator {
|
if let current = self.animator {
|
||||||
@ -852,7 +868,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateLevel(_ level: CGFloat) {
|
func updateLevel(_ level: CGFloat) {
|
||||||
let maxLevel: CGFloat = 1.0
|
let maxLevel: CGFloat = 6.0
|
||||||
let normalizedLevel = min(1, max(level / maxLevel, 0))
|
let normalizedLevel = min(1, max(level / maxLevel, 0))
|
||||||
|
|
||||||
self.backgroundNode.audioLevel = normalizedLevel
|
self.backgroundNode.audioLevel = normalizedLevel
|
||||||
@ -945,7 +961,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension UIBezierPath {
|
extension UIBezierPath {
|
||||||
static func smoothCurve(through points: [CGPoint], length: CGFloat, smoothness: CGFloat) -> UIBezierPath {
|
static func smoothCurve(through points: [CGPoint], length: CGFloat, smoothness: CGFloat, curve: Bool = false) -> UIBezierPath {
|
||||||
var smoothPoints = [SmoothPoint]()
|
var smoothPoints = [SmoothPoint]()
|
||||||
for index in (0 ..< points.count) {
|
for index in (0 ..< points.count) {
|
||||||
let prevIndex = index - 1
|
let prevIndex = index - 1
|
||||||
@ -976,14 +992,24 @@ extension UIBezierPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resultPath = UIBezierPath()
|
let resultPath = UIBezierPath()
|
||||||
resultPath.move(to: smoothPoints[0].point)
|
if curve {
|
||||||
for index in (0 ..< smoothPoints.count) {
|
resultPath.move(to: CGPoint())
|
||||||
|
resultPath.addLine(to: smoothPoints[0].point)
|
||||||
|
} else {
|
||||||
|
resultPath.move(to: smoothPoints[0].point)
|
||||||
|
}
|
||||||
|
|
||||||
|
let smoothCount = curve ? smoothPoints.count - 1 : smoothPoints.count
|
||||||
|
for index in (0 ..< smoothCount) {
|
||||||
let curr = smoothPoints[index]
|
let curr = smoothPoints[index]
|
||||||
let next = smoothPoints[(index + 1) % points.count]
|
let next = smoothPoints[(index + 1) % points.count]
|
||||||
let currSmoothOut = curr.smoothOut()
|
let currSmoothOut = curr.smoothOut()
|
||||||
let nextSmoothIn = next.smoothIn()
|
let nextSmoothIn = next.smoothIn()
|
||||||
resultPath.addCurve(to: next.point, controlPoint1: currSmoothOut, controlPoint2: nextSmoothIn)
|
resultPath.addCurve(to: next.point, controlPoint1: currSmoothOut, controlPoint2: nextSmoothIn)
|
||||||
}
|
}
|
||||||
|
if curve {
|
||||||
|
resultPath.addLine(to: CGPoint(x: length, y: 0.0))
|
||||||
|
}
|
||||||
resultPath.close()
|
resultPath.close()
|
||||||
return resultPath
|
return resultPath
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user