mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Merge branch 'master' into videochats
This commit is contained in:
@@ -27,6 +27,8 @@ private let blobSize = CGSize(width: 190.0, height: 190.0)
|
||||
private let smallScale: CGFloat = 0.48
|
||||
private let smallIconScale: CGFloat = 0.69
|
||||
|
||||
private let buttonHeight: CGFloat = 52.0
|
||||
|
||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
enum State: Equatable {
|
||||
enum ActiveState: Equatable {
|
||||
@@ -34,7 +36,15 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
case muted
|
||||
case on
|
||||
}
|
||||
|
||||
enum ScheduledState: Equatable {
|
||||
case start
|
||||
case subscribe
|
||||
case unsubscribe
|
||||
}
|
||||
|
||||
case button(text: String)
|
||||
case scheduled(state: ScheduledState)
|
||||
case connecting
|
||||
case active(state: ActiveState)
|
||||
}
|
||||
@@ -51,8 +61,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: VoiceChatActionButtonBackgroundNode
|
||||
private let iconNode: VoiceChatActionButtonIconNode
|
||||
private let titleLabel: ImmediateTextNode
|
||||
let titleLabel: ImmediateTextNode
|
||||
private let subtitleLabel: ImmediateTextNode
|
||||
private let buttonTitleLabel: ImmediateTextNode
|
||||
|
||||
private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, dark: Bool, small: Bool, title: String, subtitle: String, snap: Bool)?
|
||||
|
||||
@@ -103,7 +114,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .connecting:
|
||||
case .connecting, .button, .scheduled:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
@@ -121,12 +132,17 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
init() {
|
||||
self.bottomNode = ASDisplayNode()
|
||||
self.bottomNode.isUserInteractionEnabled = false
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.isUserInteractionEnabled = false
|
||||
self.backgroundNode = VoiceChatActionButtonBackgroundNode()
|
||||
self.iconNode = VoiceChatActionButtonIconNode(isColored: false)
|
||||
|
||||
self.titleLabel = ImmediateTextNode()
|
||||
self.subtitleLabel = ImmediateTextNode()
|
||||
self.buttonTitleLabel = ImmediateTextNode()
|
||||
self.buttonTitleLabel.isUserInteractionEnabled = false
|
||||
self.buttonTitleLabel.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -138,26 +154,38 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.iconNode)
|
||||
|
||||
self.containerNode.addSubnode(self.buttonTitleLabel)
|
||||
|
||||
self.highligthedChanged = { [weak self] pressing in
|
||||
if let strongSelf = self {
|
||||
guard let (_, _, _, _, small, _, _, snap) = strongSelf.currentParams else {
|
||||
guard let (_, _, state, _, small, _, _, snap) = strongSelf.currentParams else {
|
||||
return
|
||||
}
|
||||
if pressing {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
if small {
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale * 0.9)
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale * 0.9)
|
||||
if case .button = state {
|
||||
strongSelf.containerNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.containerNode.alpha = 0.4
|
||||
} else {
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
if small {
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale * 0.9)
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale * 0.9)
|
||||
} else {
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
|
||||
}
|
||||
}
|
||||
} else if !strongSelf.pressing {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
if small {
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale)
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale)
|
||||
if case .button = state {
|
||||
strongSelf.containerNode.alpha = 1.0
|
||||
strongSelf.containerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
} else {
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
if small {
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale)
|
||||
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale)
|
||||
} else {
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,7 +242,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 70.0), size: titleSize)
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - totalHeight) / 2.0) + 84.0), size: titleSize)
|
||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||
|
||||
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
@@ -232,7 +260,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .connecting:
|
||||
case .connecting, .button, .scheduled:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -272,6 +300,17 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
let icon: VoiceChatActionButtonIconAnimationState
|
||||
switch state {
|
||||
case .button:
|
||||
icon = .empty
|
||||
case let .scheduled(state):
|
||||
switch state {
|
||||
case .start:
|
||||
icon = .start
|
||||
case .subscribe:
|
||||
icon = .subscribe
|
||||
case .unsubscribe:
|
||||
icon = .unsubscribe
|
||||
}
|
||||
case let .active(state):
|
||||
switch state {
|
||||
case .on:
|
||||
@@ -312,8 +351,30 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
self.statePromise.set(state)
|
||||
|
||||
if let previousState = previousState, case .button = previousState, case .scheduled = state {
|
||||
self.buttonTitleLabel.alpha = 0.0
|
||||
self.buttonTitleLabel.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
self.buttonTitleLabel.layer.animateScale(from: 1.0, to: 0.001, duration: 0.24)
|
||||
|
||||
self.iconNode.alpha = 1.0
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.iconNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0)
|
||||
}
|
||||
|
||||
var backgroundState: VoiceChatActionButtonBackgroundNode.State
|
||||
var animated = true
|
||||
switch state {
|
||||
case let .button(text):
|
||||
backgroundState = .button
|
||||
self.buttonTitleLabel.alpha = 1.0
|
||||
self.buttonTitleLabel.attributedText = NSAttributedString(string: text, font: Font.semibold(17.0), textColor: .white)
|
||||
let titleSize = self.buttonTitleLabel.updateLayout(CGSize(width: size.width, height: 100.0))
|
||||
self.buttonTitleLabel.frame = CGRect(origin: CGPoint(x: floor((self.bounds.width - titleSize.width) / 2.0), y: floor((self.bounds.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
case .scheduled:
|
||||
backgroundState = .disabled
|
||||
if previousState == .connecting {
|
||||
animated = false
|
||||
}
|
||||
case let .active(state):
|
||||
switch state {
|
||||
case .on:
|
||||
@@ -330,7 +391,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
self.backgroundNode.glowHidden = (self.currentParams?.snap ?? false) || small
|
||||
self.backgroundNode.isDark = dark
|
||||
self.backgroundNode.update(state: backgroundState, animated: true)
|
||||
self.backgroundNode.update(state: backgroundState, animated: animated)
|
||||
|
||||
if case .active = state, let previousState = previousState, case .connecting = previousState, animated {
|
||||
self.activeDisposable.set((self.activePromise.get()
|
||||
@@ -341,14 +402,18 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
applyParams(animated: animated)
|
||||
self.applyParams(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
var hitRect = self.bounds
|
||||
if let (_, buttonSize, _, _, _, _, _, _) = self.currentParams {
|
||||
hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0)
|
||||
if let (_, buttonSize, state, _, _, _, _, _) = self.currentParams {
|
||||
if case .button = state {
|
||||
hitRect = CGRect(x: 0.0, y: floor((self.bounds.height - buttonHeight) / 2.0), width: self.bounds.width, height: buttonHeight)
|
||||
} else {
|
||||
hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0)
|
||||
}
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
if !hitRect.contains(point) {
|
||||
@@ -454,6 +519,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
enum State: Equatable {
|
||||
case connecting
|
||||
case disabled
|
||||
case button
|
||||
case blob(Bool)
|
||||
}
|
||||
|
||||
@@ -547,9 +613,11 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.maskProgressLayer.lineCap = .round
|
||||
self.maskProgressLayer.path = path
|
||||
|
||||
let largerCirclePath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width + progressLineWidth, height: buttonSize.height + progressLineWidth))).cgPath
|
||||
self.maskCircleLayer.fillColor = white.cgColor
|
||||
let circleFrame = CGRect(origin: CGPoint(x: (areaSize.width - buttonSize.width) / 2.0, y: (areaSize.height - buttonSize.height) / 2.0), size: buttonSize).insetBy(dx: -progressLineWidth / 2.0, dy: -progressLineWidth / 2.0)
|
||||
let largerCirclePath = UIBezierPath(roundedRect: CGRect(x: circleFrame.minX, y: circleFrame.minY, width: circleFrame.width, height: circleFrame.height), cornerRadius: circleFrame.width / 2.0).cgPath
|
||||
|
||||
self.maskCircleLayer.path = largerCirclePath
|
||||
self.maskCircleLayer.fillColor = white.cgColor
|
||||
self.maskCircleLayer.isHidden = true
|
||||
|
||||
updateInHierarchy = { [weak self] value in
|
||||
@@ -590,11 +658,11 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
let previousValue = self.foregroundGradientLayer.startPoint
|
||||
let newValue: CGPoint
|
||||
if self.maskBlobView.presentationAudioLevel > 0.22 {
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.9 ..< 1.0), y: CGFloat.random(in: 0.1 ..< 0.35))
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.9 ..< 1.0), y: CGFloat.random(in: 0.15 ..< 0.35))
|
||||
} else if self.maskBlobView.presentationAudioLevel > 0.01 {
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.77 ..< 0.95), y: CGFloat.random(in: 0.1 ..< 0.35))
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.57 ..< 0.85), y: CGFloat.random(in: 0.15 ..< 0.45))
|
||||
} else {
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.65 ..< 0.85), y: CGFloat.random(in: 0.1 ..< 0.45))
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.6 ..< 0.75), y: CGFloat.random(in: 0.25 ..< 0.45))
|
||||
}
|
||||
self.foregroundGradientLayer.startPoint = newValue
|
||||
|
||||
@@ -693,7 +761,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
case muted
|
||||
}
|
||||
|
||||
func updateGlowAndGradientAnimations(type: Gradient, previousType: Gradient? = nil) {
|
||||
func updateGlowAndGradientAnimations(type: Gradient, previousType: Gradient? = nil, animated: Bool = true) {
|
||||
let effectivePreviousTyoe = previousType ?? .active
|
||||
|
||||
let scale: CGFloat
|
||||
@@ -737,12 +805,14 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.maskGradientLayer.transform = CATransform3DMakeScale(targetScale, targetScale, 1.0)
|
||||
if let _ = previousType {
|
||||
self.maskGradientLayer.animateScale(from: initialScale, to: targetScale, duration: 0.3)
|
||||
} else {
|
||||
} else if animated {
|
||||
self.maskGradientLayer.animateSpring(from: initialScale as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
|
||||
self.foregroundGradientLayer.colors = targetColors
|
||||
self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||
if animated {
|
||||
self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
private func playMuteAnimation() {
|
||||
@@ -831,7 +901,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.maskBlobView.startAnimating()
|
||||
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
|
||||
|
||||
private func playConnectionAnimation(type: Gradient, completion: @escaping () -> Void) {
|
||||
CATransaction.begin()
|
||||
let initialRotation: CGFloat = CGFloat((self.maskProgressLayer.value(forKeyPath: "presentationLayer.transform.rotation.z") as? NSNumber)?.floatValue ?? 0.0)
|
||||
@@ -878,7 +948,8 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
self.updateGlowAndGradientAnimations(type: type, previousType: nil)
|
||||
|
||||
if case .blob = self.state {
|
||||
if case .connecting = self.state {
|
||||
} else {
|
||||
self.maskBlobView.isHidden = false
|
||||
self.maskBlobView.startAnimating()
|
||||
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
@@ -913,6 +984,53 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
private var maskIsCircle = true
|
||||
private func setupButtonAnimation() {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
self.backgroundCircleLayer.isHidden = true
|
||||
self.foregroundCircleLayer.isHidden = true
|
||||
self.maskCircleLayer.isHidden = false
|
||||
self.maskProgressLayer.isHidden = true
|
||||
self.maskGradientLayer.isHidden = true
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: floor((self.bounds.height - buttonHeight) / 2.0), width: self.bounds.width, height: buttonHeight), cornerRadius: 10.0).cgPath
|
||||
self.maskCircleLayer.path = path
|
||||
self.maskIsCircle = false
|
||||
|
||||
CATransaction.commit()
|
||||
|
||||
self.updateGlowAndGradientAnimations(type: .muted, previousType: nil)
|
||||
|
||||
self.updatedActive?(true)
|
||||
}
|
||||
|
||||
private func playScheduledAnimation() {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
self.maskGradientLayer.isHidden = false
|
||||
CATransaction.commit()
|
||||
|
||||
let circleFrame = CGRect(origin: CGPoint(x: (self.bounds.width - buttonSize.width) / 2.0, y: (self.bounds.height - buttonSize.height) / 2.0), size: buttonSize).insetBy(dx: -progressLineWidth / 2.0, dy: -progressLineWidth / 2.0)
|
||||
let largerCirclePath = UIBezierPath(roundedRect: CGRect(x: circleFrame.minX, y: circleFrame.minY, width: circleFrame.width, height: circleFrame.height), cornerRadius: circleFrame.width / 2.0).cgPath
|
||||
|
||||
let previousPath = self.maskCircleLayer.path
|
||||
self.maskCircleLayer.path = largerCirclePath
|
||||
self.maskIsCircle = true
|
||||
|
||||
self.maskCircleLayer.animateSpring(from: previousPath as AnyObject, to: largerCirclePath as AnyObject, keyPath: "path", duration: 0.6, initialVelocity: 0.0, damping: 100.0)
|
||||
|
||||
self.maskBlobView.isHidden = false
|
||||
self.maskBlobView.startAnimating()
|
||||
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, damping: 100.0)
|
||||
|
||||
self.disableGlowAnimations = true
|
||||
self.maskGradientLayer.removeAllAnimations()
|
||||
self.maskGradientLayer.animateSpring(from: 0.3 as NSNumber, to: 0.85 as NSNumber, keyPath: "transform.scale", duration: 0.45, completion: { [weak self] _ in
|
||||
self?.disableGlowAnimations = false
|
||||
})
|
||||
}
|
||||
|
||||
var isActive = false
|
||||
func updateAnimations() {
|
||||
if !self.isCurrentlyInHierarchy {
|
||||
@@ -965,7 +1083,9 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.isActive = false
|
||||
|
||||
if let transition = self.transition {
|
||||
if case .connecting = transition {
|
||||
if case .button = transition {
|
||||
self.playScheduledAnimation()
|
||||
} else if case .connecting = transition {
|
||||
self.playConnectionAnimation(type: .muted) { [weak self] in
|
||||
self?.isActive = false
|
||||
}
|
||||
@@ -974,8 +1094,21 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.playMuteAnimation()
|
||||
}
|
||||
self.transition = nil
|
||||
} else {
|
||||
if self.maskBlobView.isHidden {
|
||||
self.updateGlowAndGradientAnimations(type: .muted, previousType: nil, animated: false)
|
||||
self.maskCircleLayer.isHidden = false
|
||||
self.maskProgressLayer.isHidden = true
|
||||
self.maskGradientLayer.isHidden = false
|
||||
self.maskBlobView.isHidden = false
|
||||
self.maskBlobView.startAnimating()
|
||||
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
}
|
||||
break
|
||||
case .button:
|
||||
self.updatedActive?(true)
|
||||
self.isActive = false
|
||||
self.setupButtonAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,23 +1173,41 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.updateAnimations()
|
||||
}
|
||||
|
||||
var previousSize: CGSize?
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||
let sizeUpdated = self.previousSize != self.bounds.size
|
||||
self.previousSize = self.bounds.size
|
||||
|
||||
let circleFrame = CGRect(origin: CGPoint(x: (self.bounds.width - buttonSize.width) / 2.0, y: (self.bounds.height - buttonSize.height) / 2.0), size: buttonSize)
|
||||
let bounds = CGRect(x: (self.bounds.width - areaSize.width) / 2.0, y: (self.bounds.height - areaSize.height) / 2.0, width: areaSize.width, height: areaSize.height)
|
||||
let center = bounds.center
|
||||
|
||||
self.maskBlobView.frame = CGRect(origin: CGPoint(x: bounds.minX + (bounds.width - blobSize.width) / 2.0, y: bounds.minY + (bounds.height - blobSize.height) / 2.0), size: blobSize)
|
||||
|
||||
let circleFrame = CGRect(origin: CGPoint(x: bounds.minX + (bounds.width - buttonSize.width) / 2.0, y: bounds.minY + (bounds.height - buttonSize.height) / 2.0), size: buttonSize)
|
||||
self.backgroundCircleLayer.frame = circleFrame
|
||||
self.foregroundCircleLayer.position = center
|
||||
self.foregroundCircleLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: circleFrame.width - progressLineWidth, height: circleFrame.height - progressLineWidth))
|
||||
self.growingForegroundCircleLayer.position = center
|
||||
self.growingForegroundCircleLayer.bounds = self.foregroundCircleLayer.bounds
|
||||
self.maskCircleLayer.frame = circleFrame.insetBy(dx: -progressLineWidth / 2.0, dy: -progressLineWidth / 2.0)
|
||||
self.maskCircleLayer.frame = self.bounds
|
||||
|
||||
if sizeUpdated && self.maskIsCircle {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
let circleFrame = CGRect(origin: CGPoint(x: (self.bounds.width - buttonSize.width) / 2.0, y: (self.bounds.height - buttonSize.height) / 2.0), size: buttonSize).insetBy(dx: -progressLineWidth / 2.0, dy: -progressLineWidth / 2.0)
|
||||
let largerCirclePath = UIBezierPath(roundedRect: CGRect(x: circleFrame.minX, y: circleFrame.minY, width: circleFrame.width, height: circleFrame.height), cornerRadius: circleFrame.width / 2.0).cgPath
|
||||
|
||||
self.maskCircleLayer.path = largerCirclePath
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
self.maskProgressLayer.frame = circleFrame.insetBy(dx: -3.0, dy: -3.0)
|
||||
self.foregroundView.frame = self.bounds
|
||||
self.foregroundGradientLayer.frame = self.bounds
|
||||
self.maskGradientLayer.position = center
|
||||
self.maskGradientLayer.bounds = self.bounds
|
||||
self.maskGradientLayer.bounds = bounds
|
||||
self.maskView.frame = self.bounds
|
||||
}
|
||||
}
|
||||
@@ -1392,6 +1543,10 @@ final class BlobView: UIView {
|
||||
}
|
||||
|
||||
enum VoiceChatActionButtonIconAnimationState: Equatable {
|
||||
case empty
|
||||
case start
|
||||
case subscribe
|
||||
case unsubscribe
|
||||
case unmute
|
||||
case mute
|
||||
case hand
|
||||
@@ -1416,30 +1571,77 @@ final class VoiceChatActionButtonIconNode: ManagedAnimationNode {
|
||||
let previousState = self.iconState
|
||||
self.iconState = state
|
||||
|
||||
if state != .empty {
|
||||
self.alpha = 1.0
|
||||
}
|
||||
switch previousState {
|
||||
case .empty:
|
||||
switch state {
|
||||
case .start:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceStart"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.001))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .subscribe:
|
||||
switch state {
|
||||
case .unsubscribe:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceCancelReminder")))
|
||||
case .mute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceSetReminderToMute")))
|
||||
case .hand:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceSetReminderToRaiseHand")))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .unsubscribe:
|
||||
switch state {
|
||||
case .subscribe:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceSetReminder")))
|
||||
case .mute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceCancelReminderToMute")))
|
||||
case .hand:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceCancelReminderToRaiseHand")))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .start:
|
||||
switch state {
|
||||
case .mute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceStart")))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .unmute:
|
||||
switch state {
|
||||
case .mute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceMute")))
|
||||
case .hand:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceHandOff2")))
|
||||
case .unmute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceUnmuteToRaiseHand")))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .mute:
|
||||
switch state {
|
||||
case .start:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceStart"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.001))
|
||||
case .unmute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceUnmute"), frames: .range(startFrame: 0, endFrame: 12), duration: 0.2))
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceUnmute")))
|
||||
case .hand:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceHandOff")))
|
||||
case .mute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceMuteToRaiseHand")))
|
||||
case .subscribe:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceSetReminderToRaiseHand"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.001))
|
||||
case .unsubscribe:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceCancelReminderToRaiseHand"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.001))
|
||||
case .empty:
|
||||
self.alpha = 0.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .hand:
|
||||
switch state {
|
||||
case .mute, .unmute:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceHandOn")))
|
||||
case .hand:
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceRaiseHandToMute")))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1452,15 +1654,25 @@ final class VoiceChatActionButtonIconNode: ManagedAnimationNode {
|
||||
}
|
||||
|
||||
var useTiredAnimation = false
|
||||
var useAngryAnimation = false
|
||||
let val = Float.random(in: 0.0..<1.0)
|
||||
if val <= 0.01 {
|
||||
useTiredAnimation = true
|
||||
} else if val <= 0.05 {
|
||||
useAngryAnimation = true
|
||||
}
|
||||
|
||||
let normalAnimations = ["VoiceHand_1", "VoiceHand_2", "VoiceHand_3", "VoiceHand_4", "VoiceHand_7"]
|
||||
let normalAnimations = ["VoiceHand_1", "VoiceHand_2", "VoiceHand_3", "VoiceHand_4", "VoiceHand_7", "VoiceHand_8"]
|
||||
let tiredAnimations = ["VoiceHand_5", "VoiceHand_6"]
|
||||
let animations = useTiredAnimation ? tiredAnimations : normalAnimations
|
||||
|
||||
let angryAnimations = ["VoiceHand_9", "VoiceHand_10"]
|
||||
let animations: [String]
|
||||
if useTiredAnimation {
|
||||
animations = tiredAnimations
|
||||
} else if useAngryAnimation {
|
||||
animations = angryAnimations
|
||||
} else {
|
||||
animations = normalAnimations
|
||||
}
|
||||
if let animationName = animations.randomElement() {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local(animationName)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user