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
7f98f8918c
commit
6b75d36548
@ -442,6 +442,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
private var isHighlighted: Bool = false
|
||||
private var skipFadeout: Bool = false
|
||||
|
||||
private var onlineIsVoiceChat: Bool = false
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
if self.selectableControlNode != nil || self.item?.editing == true {
|
||||
return false
|
||||
@ -695,7 +697,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateAlpha(layer: self.highlightedBackgroundNode.layer, alpha: highlightProgress)
|
||||
|
||||
if let item = self.item {
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted))
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: self.onlineIsVoiceChat), color: nil)
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
@ -711,11 +713,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let item = self.item {
|
||||
let onlineIcon: UIImage?
|
||||
if item.index.pinningIndex != nil {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned)
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat)
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular)
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat)
|
||||
}
|
||||
self.onlineNode.setImage(onlineIcon)
|
||||
self.onlineNode.setImage(onlineIcon, color: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1439,6 +1441,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.currentItemHeight = itemHeight
|
||||
strongSelf.cachedChatListText = chatListText
|
||||
strongSelf.cachedChatListSearchResult = chatListSearchResult
|
||||
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
|
||||
|
||||
strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
|
||||
@ -1524,18 +1527,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
||||
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
|
||||
|
||||
let onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width - 2.0, y: avatarFrame.maxY - onlineLayout.height - 2.0), size: onlineLayout)
|
||||
let onlineFrame: CGRect
|
||||
if onlineIsVoiceChat {
|
||||
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width + 1.0 - UIScreenPixel, y: avatarFrame.maxY - onlineLayout.height + 1.0 - UIScreenPixel), size: onlineLayout)
|
||||
} else {
|
||||
onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width - 2.0, y: avatarFrame.maxY - onlineLayout.height - 2.0), size: onlineLayout)
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.onlineNode, frame: onlineFrame)
|
||||
|
||||
let onlineIcon: UIImage?
|
||||
if strongSelf.reallyHighlighted {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted)
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: onlineIsVoiceChat)
|
||||
} else if item.index.pinningIndex != nil {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned)
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: onlineIsVoiceChat)
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular)
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: onlineIsVoiceChat)
|
||||
}
|
||||
strongSelf.onlineNode.setImage(onlineIcon)
|
||||
strongSelf.onlineNode.setImage(onlineIcon, color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
let _ = measureApply()
|
||||
let _ = dateApply()
|
||||
|
@ -209,7 +209,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
strongSelf.badgeBackgroundNode.isHidden = true
|
||||
}
|
||||
|
||||
strongSelf.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.theme, state: .regular))
|
||||
strongSelf.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.theme, state: .regular), color: nil)
|
||||
strongSelf.onlineNode.frame = CGRect(x: itemLayout.size.width - onlineLayout.width - 18.0, y: itemLayout.size.height - onlineLayout.height - 18.0, width: onlineLayout.width, height: onlineLayout.height)
|
||||
|
||||
let _ = badgeApply()
|
||||
|
@ -3,8 +3,106 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private final class VoiceChatIndicatorNode: ASDisplayNode {
|
||||
private let leftLine: ASDisplayNode
|
||||
private let centerLine: ASDisplayNode
|
||||
private let rightLine: ASDisplayNode
|
||||
|
||||
private var isCurrentlyInHierarchy = false
|
||||
private var shouldBeAnimating = false
|
||||
|
||||
var color: UIColor = UIColor(rgb: 0xffffff) {
|
||||
didSet {
|
||||
self.leftLine.backgroundColor = self.color
|
||||
self.centerLine.backgroundColor = self.color
|
||||
self.rightLine.backgroundColor = self.color
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.leftLine = ASDisplayNode()
|
||||
self.leftLine.isLayerBacked = true
|
||||
self.leftLine.cornerRadius = 1.0
|
||||
self.leftLine.frame = CGRect(x: 6.0, y: 6.0, width: 2.0, height: 10.0)
|
||||
|
||||
self.centerLine = ASDisplayNode()
|
||||
self.centerLine.isLayerBacked = true
|
||||
self.centerLine.cornerRadius = 1.0
|
||||
self.centerLine.frame = CGRect(x: 10.0, y: 5.0, width: 2.0, height: 12.0)
|
||||
|
||||
self.rightLine = ASDisplayNode()
|
||||
self.rightLine.isLayerBacked = true
|
||||
self.rightLine.cornerRadius = 1.0
|
||||
self.rightLine.frame = CGRect(x: 14.0, y: 6.0, width: 2.0, height: 10.0)
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
|
||||
self.addSubnode(self.leftLine)
|
||||
self.addSubnode(self.centerLine)
|
||||
self.addSubnode(self.rightLine)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = true
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = false
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
let timingFunctions: [CAMediaTimingFunction] = (0 ..< 5).map { _ in CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) }
|
||||
|
||||
let leftAnimation = CAKeyframeAnimation(keyPath: "bounds.size.height")
|
||||
leftAnimation.timingFunctions = timingFunctions
|
||||
leftAnimation.values = [NSNumber(value: 10.0), NSNumber(value: 4.0), NSNumber(value: 8.0), NSNumber(value: 4.0), NSNumber(value: 10.0)]
|
||||
leftAnimation.repeatCount = Float.infinity
|
||||
leftAnimation.duration = 2.0
|
||||
self.leftLine.layer.add(leftAnimation, forKey: "animation")
|
||||
|
||||
let centerAnimation = CAKeyframeAnimation(keyPath: "bounds.size.height")
|
||||
centerAnimation.timingFunctions = timingFunctions
|
||||
centerAnimation.values = [NSNumber(value: 6.0), NSNumber(value: 10.0), NSNumber(value: 4.0), NSNumber(value: 12.0), NSNumber(value: 6.0)]
|
||||
centerAnimation.repeatCount = Float.infinity
|
||||
centerAnimation.duration = 2.0
|
||||
self.centerLine.layer.add(centerAnimation, forKey: "animation")
|
||||
|
||||
let rightAnimation = CAKeyframeAnimation(keyPath: "bounds.size.height")
|
||||
rightAnimation.timingFunctions = timingFunctions
|
||||
rightAnimation.values = [NSNumber(value: 10.0), NSNumber(value: 4.0), NSNumber(value: 8.0), NSNumber(value: 4.0), NSNumber(value: 10.0)]
|
||||
rightAnimation.repeatCount = Float.infinity
|
||||
rightAnimation.duration = 2.0
|
||||
self.rightLine.layer.add(rightAnimation, forKey: "animation")
|
||||
} else {
|
||||
self.leftLine.layer.removeAnimation(forKey: "animation")
|
||||
self.centerLine.layer.removeAnimation(forKey: "animation")
|
||||
self.rightLine.layer.removeAnimation(forKey: "animation")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeerOnlineMarkerNode: ASDisplayNode {
|
||||
private let iconNode: ASImageNode
|
||||
private var animationNode: VoiceChatIndicatorNode?
|
||||
|
||||
private var color: UIColor = UIColor(rgb: 0xffffff) {
|
||||
didSet {
|
||||
self.animationNode?.color = self.color
|
||||
}
|
||||
}
|
||||
|
||||
override public init() {
|
||||
self.iconNode = ASImageNode()
|
||||
@ -20,16 +118,31 @@ public final class PeerOnlineMarkerNode: ASDisplayNode {
|
||||
self.addSubnode(self.iconNode)
|
||||
}
|
||||
|
||||
public func setImage(_ image: UIImage?) {
|
||||
public func setImage(_ image: UIImage?, color: UIColor?) {
|
||||
self.iconNode.image = image
|
||||
if let color = color {
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (Bool, Bool) -> (CGSize, (Bool) -> Void) {
|
||||
return { [weak self] online, isVoiceChat in
|
||||
return (CGSize(width: 14.0, height: 14.0), { animated in
|
||||
let size: CGFloat = isVoiceChat ? 22.0 : 14.0
|
||||
return (CGSize(width: size, height: size), { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.iconNode.frame = CGRect(x: 0.0, y: 0.0, width: 14.0, height: 14.0)
|
||||
strongSelf.iconNode.frame = CGRect(x: 0.0, y: 0.0, width: size, height: size)
|
||||
|
||||
if isVoiceChat {
|
||||
if let _ = strongSelf.animationNode {
|
||||
} else {
|
||||
let animationNode = VoiceChatIndicatorNode()
|
||||
animationNode.color = strongSelf.color
|
||||
animationNode.frame = strongSelf.iconNode.bounds
|
||||
strongSelf.animationNode = animationNode
|
||||
strongSelf.iconNode.addSubnode(animationNode)
|
||||
}
|
||||
}
|
||||
|
||||
if animated {
|
||||
let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.0 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
||||
let targetScale: CGFloat = online ? 1.0 : 0.0
|
||||
@ -37,10 +150,18 @@ public final class PeerOnlineMarkerNode: ASDisplayNode {
|
||||
strongSelf.iconNode.layer.animateScale(from: initialScale, to: targetScale, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
if let strongSelf = self, finished {
|
||||
strongSelf.iconNode.isHidden = !online
|
||||
|
||||
if let animationNode = strongSelf.animationNode, !isVoiceChat {
|
||||
animationNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.iconNode.isHidden = !online
|
||||
|
||||
if let animationNode = strongSelf.animationNode, !isVoiceChat {
|
||||
animationNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -166,7 +166,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
let (onlineSize, onlineApply) = onlineLayout(online, false)
|
||||
let _ = onlineApply(false)
|
||||
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel))
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel), color: nil)
|
||||
self.onlineNode.frame = CGRect(origin: CGPoint(), size: onlineSize)
|
||||
|
||||
self.setNeedsLayout()
|
||||
|
@ -25,6 +25,178 @@ private class CallStatusBarBackgroundNodeDrawingState: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private final class Curve {
|
||||
let pointsCount: Int
|
||||
let smoothness: CGFloat
|
||||
|
||||
let minRandomness: CGFloat
|
||||
let maxRandomness: CGFloat
|
||||
|
||||
let minSpeed: CGFloat
|
||||
let maxSpeed: CGFloat
|
||||
|
||||
let size: CGSize
|
||||
var currentOffset: CGFloat = 1.0
|
||||
var minOffset: CGFloat = 0.0
|
||||
var maxOffset: CGFloat = 2.0
|
||||
let scaleSpeed: CGFloat
|
||||
|
||||
private var speedLevel: CGFloat = 0.0
|
||||
private var lastSpeedLevel: CGFloat = 0.0
|
||||
|
||||
private var fromPoints: [CGPoint]?
|
||||
private var toPoints: [CGPoint]?
|
||||
|
||||
private var currentPoints: [CGPoint]? {
|
||||
guard let fromPoints = self.fromPoints, let toPoints = self.toPoints else { return nil }
|
||||
|
||||
return fromPoints.enumerated().map { offset, fromPoint in
|
||||
let toPoint = toPoints[offset]
|
||||
return CGPoint(x: fromPoint.x + (toPoint.x - fromPoint.x) * transition, y: fromPoint.y + (toPoint.y - fromPoint.y) * transition)
|
||||
}
|
||||
}
|
||||
|
||||
var currentShape: UIBezierPath?
|
||||
private var transition: CGFloat = 0 {
|
||||
didSet {
|
||||
if let currentPoints = self.currentPoints {
|
||||
self.currentShape = UIBezierPath.smoothCurve(through: currentPoints, length: size.width, smoothness: smoothness)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var level: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.currentOffset = self.minOffset + (self.maxOffset - self.minOffset) * self.level
|
||||
}
|
||||
}
|
||||
|
||||
private var transitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
var loop: Bool = true {
|
||||
didSet {
|
||||
if let _ = transitionArguments {
|
||||
} else {
|
||||
self.animateToNewShape()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
size: CGSize,
|
||||
pointsCount: Int,
|
||||
minRandomness: CGFloat,
|
||||
maxRandomness: CGFloat,
|
||||
minSpeed: CGFloat,
|
||||
maxSpeed: CGFloat,
|
||||
minOffset: CGFloat,
|
||||
maxOffset: CGFloat,
|
||||
scaleSpeed: CGFloat
|
||||
) {
|
||||
self.size = size
|
||||
// self.alpha = alpha
|
||||
self.pointsCount = pointsCount
|
||||
self.minRandomness = minRandomness
|
||||
self.maxRandomness = maxRandomness
|
||||
self.minSpeed = minSpeed
|
||||
self.maxSpeed = maxSpeed
|
||||
self.minOffset = minOffset
|
||||
self.maxOffset = maxOffset
|
||||
self.scaleSpeed = scaleSpeed
|
||||
|
||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
||||
|
||||
self.currentOffset = minOffset
|
||||
|
||||
self.animateToNewShape()
|
||||
}
|
||||
|
||||
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
||||
self.speedLevel = max(self.speedLevel, newSpeedLevel)
|
||||
|
||||
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 {
|
||||
animateToNewShape()
|
||||
}
|
||||
}
|
||||
|
||||
private func animateToNewShape() {
|
||||
if let _ = self.transitionArguments {
|
||||
self.fromPoints = self.currentPoints
|
||||
self.toPoints = nil
|
||||
self.transition = 0.0
|
||||
self.transitionArguments = nil
|
||||
}
|
||||
|
||||
if self.fromPoints == nil {
|
||||
self.fromPoints = generateNextCurve(for: self.size)
|
||||
}
|
||||
if self.toPoints == nil {
|
||||
self.toPoints = generateNextCurve(for: self.size)
|
||||
}
|
||||
|
||||
let duration: Double = 1.0 / Double(minSpeed + (maxSpeed - minSpeed) * speedLevel)
|
||||
self.transitionArguments = (CACurrentMediaTime(), duration)
|
||||
|
||||
self.lastSpeedLevel = self.speedLevel
|
||||
self.speedLevel = 0
|
||||
|
||||
self.updateAnimations()
|
||||
}
|
||||
|
||||
func updateAnimations() {
|
||||
var animate = false
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
|
||||
self.transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
||||
if self.transition < 1.0 {
|
||||
animate = true
|
||||
} else {
|
||||
if self.loop {
|
||||
self.animateToNewShape()
|
||||
} else {
|
||||
self.fromPoints = self.currentPoints
|
||||
self.toPoints = nil
|
||||
self.transition = 0.0
|
||||
self.transitionArguments = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
||||
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
||||
return blob(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))
|
||||
}
|
||||
}
|
||||
|
||||
private func blob(pointsCount: Int, randomness: CGFloat) -> [CGPoint] {
|
||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||
|
||||
let rgen = { () -> CGFloat in
|
||||
let accuracy: UInt32 = 1000
|
||||
let random = arc4random_uniform(accuracy)
|
||||
return CGFloat(random) / CGFloat(accuracy)
|
||||
}
|
||||
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 randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
||||
let angleRandomness: CGFloat = angle * 0.1
|
||||
let randAngle = angle + angle * ((angleRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - angleRandomness * 0.5)
|
||||
let pointX = sin(startAngle + CGFloat(i) * randAngle)
|
||||
let pointY = cos(startAngle + CGFloat(i) * randAngle)
|
||||
return CGPoint(x: pointX * randPointOffset, y: pointY * randPointOffset)
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
||||
}
|
||||
|
||||
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
var muted = true
|
||||
|
||||
|
@ -40,19 +40,26 @@ private enum VoiceChatActionButtonBackgroundNodeType {
|
||||
case blob
|
||||
}
|
||||
|
||||
private protocol VoiceChatActionButtonBackgroundNodeContext {
|
||||
var type: VoiceChatActionButtonBackgroundNodeType { get }
|
||||
var frameInterval: Int { get }
|
||||
var isAnimating: Bool { get }
|
||||
|
||||
func updateAnimations()
|
||||
func drawingState() -> VoiceChatActionButtonBackgroundNodeState
|
||||
}
|
||||
|
||||
private protocol VoiceChatActionButtonBackgroundNodeState: NSObjectProtocol {
|
||||
var blueGradient: UIImage? { get set }
|
||||
var greenGradient: UIImage? { get set }
|
||||
|
||||
var frameInterval: Int { get }
|
||||
var isAnimating: Bool { get }
|
||||
var type: VoiceChatActionButtonBackgroundNodeType { get }
|
||||
func updateAnimations()
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeConnectingState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
private final class VoiceChatActionButtonBackgroundNodeConnectingContext: VoiceChatActionButtonBackgroundNodeContext {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
init(blueGradient: UIImage?) {
|
||||
self.blueGradient = blueGradient
|
||||
}
|
||||
|
||||
var isAnimating: Bool {
|
||||
return true
|
||||
@ -69,15 +76,21 @@ private final class VoiceChatActionButtonBackgroundNodeConnectingState: NSObject
|
||||
func updateAnimations() {
|
||||
}
|
||||
|
||||
func drawingState() -> VoiceChatActionButtonBackgroundNodeState {
|
||||
return VoiceChatActionButtonBackgroundNodeConnectingState(blueGradient: self.blueGradient)
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeConnectingState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
init(blueGradient: UIImage?) {
|
||||
self.blueGradient = blueGradient
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeDisabledState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeDisabledContext: VoiceChatActionButtonBackgroundNodeContext {
|
||||
var isAnimating: Bool {
|
||||
return false
|
||||
}
|
||||
@ -92,6 +105,15 @@ private final class VoiceChatActionButtonBackgroundNodeDisabledState: NSObject,
|
||||
|
||||
func updateAnimations() {
|
||||
}
|
||||
|
||||
func drawingState() -> VoiceChatActionButtonBackgroundNodeState {
|
||||
return VoiceChatActionButtonBackgroundNodeDisabledState()
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeDisabledState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
}
|
||||
|
||||
private final class Blob {
|
||||
@ -305,7 +327,7 @@ private final class Blob {
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
private final class VoiceChatActionButtonBackgroundNodeBlobContext: VoiceChatActionButtonBackgroundNodeContext {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
@ -321,13 +343,15 @@ private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, Voic
|
||||
return .blob
|
||||
}
|
||||
|
||||
typealias BlobRange = (min: CGFloat, max: CGFloat)
|
||||
let blobs: [Blob]
|
||||
|
||||
let size: CGSize
|
||||
var active: Bool
|
||||
var activeTransitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
typealias BlobRange = (min: CGFloat, max: CGFloat)
|
||||
let blobs: [Blob]
|
||||
|
||||
init(size: CGSize, active: Bool, blueGradient: UIImage, greenGradient: UIImage) {
|
||||
self.size = size
|
||||
self.active = active
|
||||
self.blueGradient = blueGradient
|
||||
self.greenGradient = greenGradient
|
||||
@ -340,8 +364,8 @@ private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, Voic
|
||||
|
||||
self.blobs = [largeBlob, mediumBlob]
|
||||
}
|
||||
|
||||
func update(with state: VoiceChatActionButtonBackgroundNodeBlobState) {
|
||||
|
||||
func update(with state: VoiceChatActionButtonBackgroundNodeBlobContext) {
|
||||
if self.active != state.active {
|
||||
self.active = state.active
|
||||
|
||||
@ -364,14 +388,69 @@ private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, Voic
|
||||
blob.updateAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
func drawingState() -> VoiceChatActionButtonBackgroundNodeState {
|
||||
var blobs: [BlobDrawingState] = []
|
||||
for blob in self.blobs {
|
||||
if let path = blob.currentShape?.copy() as? UIBezierPath {
|
||||
blobs.append(BlobDrawingState(size: blob.size, path: path, scale: blob.currentScale, alpha: blob.alpha))
|
||||
}
|
||||
}
|
||||
return VoiceChatActionButtonBackgroundNodeBlobState(size: self.size, active: self.active, activeTransitionArguments: self.activeTransitionArguments, blueGradient: self.blueGradient, greenGradient: self.greenGradient, blobs: blobs)
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeTransition {
|
||||
private class BlobDrawingState: NSObject {
|
||||
let size: CGSize
|
||||
let path: UIBezierPath
|
||||
let scale: CGFloat
|
||||
let alpha: CGFloat
|
||||
|
||||
init(size: CGSize, path: UIBezierPath, scale: CGFloat, alpha: CGFloat) {
|
||||
self.size = size
|
||||
self.path = path
|
||||
self.scale = scale
|
||||
self.alpha = alpha
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
let active: Bool
|
||||
let activeTransitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
let blobs: [BlobDrawingState]
|
||||
|
||||
init(size: CGSize, active: Bool, activeTransitionArguments: (startTime: Double, duration: Double)?, blueGradient: UIImage?, greenGradient: UIImage?, blobs: [BlobDrawingState]) {
|
||||
self.active = active
|
||||
self.activeTransitionArguments = activeTransitionArguments
|
||||
self.blueGradient = blueGradient
|
||||
self.greenGradient = greenGradient
|
||||
self.blobs = blobs
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeTransitionState: NSObject {
|
||||
let startTime: Double
|
||||
let transition: CGFloat
|
||||
let previousState: VoiceChatActionButtonBackgroundNodeType
|
||||
|
||||
init(startTime: Double, transition: CGFloat, previousState: VoiceChatActionButtonBackgroundNodeType) {
|
||||
self.startTime = startTime
|
||||
self.transition = transition
|
||||
self.previousState = previousState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeTransitionContext {
|
||||
let startTime: Double
|
||||
let duration: Double
|
||||
let previousState: VoiceChatActionButtonBackgroundNodeState?
|
||||
let previousState: VoiceChatActionButtonBackgroundNodeContext
|
||||
|
||||
init(startTime: Double, duration: Double, previousState: VoiceChatActionButtonBackgroundNodeState?) {
|
||||
init(startTime: Double, duration: Double, previousState: VoiceChatActionButtonBackgroundNodeContext) {
|
||||
self.startTime = startTime
|
||||
self.duration = duration
|
||||
self.previousState = previousState
|
||||
@ -384,15 +463,20 @@ private final class VoiceChatActionButtonBackgroundNodeTransition {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func drawingTransitionState(time: Double) -> VoiceChatActionButtonBackgroundNodeTransitionState {
|
||||
let transition = CGFloat(max(0.0, min(1.0, (time - startTime) / duration)))
|
||||
return VoiceChatActionButtonBackgroundNodeTransitionState(startTime: self.startTime, transition: transition, previousState: previousState.type)
|
||||
}
|
||||
}
|
||||
|
||||
private class VoiceChatActionButtonBackgroundNodeDrawingState: NSObject {
|
||||
let timestamp: Double
|
||||
let state: VoiceChatActionButtonBackgroundNodeState
|
||||
let simplified: Bool
|
||||
let transition: VoiceChatActionButtonBackgroundNodeTransition?
|
||||
let transition: VoiceChatActionButtonBackgroundNodeTransitionState?
|
||||
|
||||
init(timestamp: Double, state: VoiceChatActionButtonBackgroundNodeState, simplified: Bool, transition: VoiceChatActionButtonBackgroundNodeTransition?) {
|
||||
init(timestamp: Double, state: VoiceChatActionButtonBackgroundNodeState, simplified: Bool, transition: VoiceChatActionButtonBackgroundNodeTransitionState?) {
|
||||
self.timestamp = timestamp
|
||||
self.state = state
|
||||
self.simplified = simplified
|
||||
@ -401,14 +485,14 @@ private class VoiceChatActionButtonBackgroundNodeDrawingState: NSObject {
|
||||
}
|
||||
|
||||
private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
private var state: VoiceChatActionButtonBackgroundNodeState
|
||||
private var state: VoiceChatActionButtonBackgroundNodeContext
|
||||
private var hasState = false
|
||||
private var transition: VoiceChatActionButtonBackgroundNodeTransition?
|
||||
private var transition: VoiceChatActionButtonBackgroundNodeTransitionContext?
|
||||
private var simplified = false
|
||||
|
||||
var audioLevel: CGFloat = 0.0 {
|
||||
didSet {
|
||||
if let blobsState = self.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
if let blobsState = self.state as? VoiceChatActionButtonBackgroundNodeBlobContext {
|
||||
for blob in blobsState.blobs {
|
||||
blob.loop = audioLevel.isZero
|
||||
blob.updateSpeedLevel(to: self.audioLevel)
|
||||
@ -421,7 +505,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
private var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
override init() {
|
||||
self.state = VoiceChatActionButtonBackgroundNodeConnectingState(blueGradient: nil)
|
||||
self.state = VoiceChatActionButtonBackgroundNodeConnectingContext(blueGradient: nil)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -429,7 +513,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
self.displaysAsynchronously = true
|
||||
}
|
||||
|
||||
func update(state: VoiceChatActionButtonBackgroundNodeState, simplified: Bool, animated: Bool) {
|
||||
func update(state: VoiceChatActionButtonBackgroundNodeContext, simplified: Bool, animated: Bool) {
|
||||
var animated = animated
|
||||
var hadState = true
|
||||
if !self.hasState {
|
||||
@ -442,10 +526,10 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
if state.type != self.state.type || !hadState {
|
||||
if animated {
|
||||
self.transition = VoiceChatActionButtonBackgroundNodeTransition(startTime: CACurrentMediaTime(), duration: 0.3, previousState: self.state)
|
||||
self.transition = VoiceChatActionButtonBackgroundNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.3, previousState: self.state)
|
||||
}
|
||||
self.state = state
|
||||
} else if let blobState = self.state as? VoiceChatActionButtonBackgroundNodeBlobState, let nextState = state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
} else if let blobState = self.state as? VoiceChatActionButtonBackgroundNodeBlobContext, let nextState = state as? VoiceChatActionButtonBackgroundNodeBlobContext {
|
||||
blobState.update(with: nextState)
|
||||
}
|
||||
|
||||
@ -457,7 +541,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
self.presentationAudioLevel = self.presentationAudioLevel * 0.9 + max(0.1, self.audioLevel) * 0.1
|
||||
if let blobsState = self.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
if let blobsState = self.state as? VoiceChatActionButtonBackgroundNodeBlobContext {
|
||||
for blob in blobsState.blobs {
|
||||
blob.level = self.presentationAudioLevel
|
||||
}
|
||||
@ -496,14 +580,13 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return VoiceChatActionButtonBackgroundNodeDrawingState(timestamp: CACurrentMediaTime(), state: self.state, simplified: self.simplified, transition: self.transition)
|
||||
let timestamp = CACurrentMediaTime()
|
||||
return VoiceChatActionButtonBackgroundNodeDrawingState(timestamp: timestamp, state: self.state.drawingState(), simplified: self.simplified, transition: self.transition?.drawingTransitionState(time: timestamp))
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
// let drawStart = CACurrentMediaTime()
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
@ -529,8 +612,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
var appearanceProgress: CGFloat = 1.0
|
||||
var glowScale: CGFloat = 0.75
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
appearanceProgress = transition.progress(time: parameters.timestamp)
|
||||
if let transition = parameters.transition, transition.previousState == .connecting {
|
||||
appearanceProgress = transition.transition
|
||||
}
|
||||
|
||||
if let blobsState = parameters.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
@ -582,26 +665,25 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
if let blobsState = parameters.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
for blob in blobsState.blobs {
|
||||
if let path = blob.currentShape, let uiPath = path.copy() as? UIBezierPath {
|
||||
let offset = (bounds.size.width - blob.size.width) / 2.0
|
||||
let toOrigin = CGAffineTransform(translationX: -bounds.size.width / 2.0 + offset, y: -bounds.size.height / 2.0 + offset)
|
||||
let fromOrigin = CGAffineTransform(translationX: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
let uiPath = blob.path
|
||||
let offset = (bounds.size.width - blob.size.width) / 2.0
|
||||
let toOrigin = CGAffineTransform(translationX: -bounds.size.width / 2.0 + offset, y: -bounds.size.height / 2.0 + offset)
|
||||
let fromOrigin = CGAffineTransform(translationX: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
|
||||
uiPath.apply(toOrigin)
|
||||
uiPath.apply(CGAffineTransform(scaleX: blob.currentScale * appearanceProgress, y: blob.currentScale * appearanceProgress))
|
||||
uiPath.apply(fromOrigin)
|
||||
uiPath.apply(toOrigin)
|
||||
uiPath.apply(CGAffineTransform(scaleX: blob.scale * appearanceProgress, y: blob.scale * appearanceProgress))
|
||||
uiPath.apply(fromOrigin)
|
||||
|
||||
context.addPath(uiPath.cgPath)
|
||||
context.clip()
|
||||
context.addPath(uiPath.cgPath)
|
||||
context.clip()
|
||||
|
||||
context.setAlpha(blob.alpha)
|
||||
context.setAlpha(blob.alpha)
|
||||
|
||||
if parameters.simplified {
|
||||
context.setFillColor(simpleColor.cgColor)
|
||||
context.fill(bounds)
|
||||
} else if let gradient = gradientImage?.cgImage {
|
||||
context.draw(gradient, in: CGRect(origin: CGPoint(x: gradientCenter.x - gradientSize / 2.0, y: gradientCenter.y - gradientSize / 2.0), size: CGSize(width: gradientSize, height: gradientSize)))
|
||||
}
|
||||
if parameters.simplified {
|
||||
context.setFillColor(simpleColor.cgColor)
|
||||
context.fill(bounds)
|
||||
} else if let gradient = gradientImage?.cgImage {
|
||||
context.draw(gradient, in: CGRect(origin: CGPoint(x: gradientCenter.x - gradientSize / 2.0, y: gradientCenter.y - gradientSize / 2.0), size: CGSize(width: gradientSize, height: gradientSize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -614,7 +696,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
var drawGradient = false
|
||||
let lineWidth = 3.0 + UIScreenPixel
|
||||
if parameters.state is VoiceChatActionButtonBackgroundNodeConnectingState || parameters.transition?.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
if parameters.state is VoiceChatActionButtonBackgroundNodeConnectingState || parameters.transition?.previousState == .connecting {
|
||||
var globalAngle: CGFloat = CGFloat(parameters.timestamp.truncatingRemainder(dividingBy: Double.pi * 2.0))
|
||||
globalAngle *= 4.0
|
||||
globalAngle = CGFloat(globalAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2.0))
|
||||
@ -627,7 +709,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
var skip = false
|
||||
var progress = CGFloat(1.0 + timestamp.remainder(dividingBy: 2.0))
|
||||
if let transition = parameters.transition {
|
||||
var transitionProgress = transition.progress(time: parameters.timestamp)
|
||||
var transitionProgress = transition.transition
|
||||
if parameters.state is VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
transitionProgress = min(1.0, transitionProgress / 0.5)
|
||||
progress = progress + (2.0 - progress) * transitionProgress
|
||||
@ -635,7 +717,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
skip = true
|
||||
}
|
||||
} else if parameters.state is VoiceChatActionButtonBackgroundNodeDisabledState {
|
||||
progress = progress + (1.0 - progress) * transition.progress(time: parameters.timestamp)
|
||||
progress = progress + (1.0 - progress) * transition.transition
|
||||
if transitionProgress >= 1.0 {
|
||||
skip = true
|
||||
}
|
||||
@ -668,8 +750,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
path.addEllipse(in: buttonRect.insetBy(dx: -lineWidth / 2.0, dy: -lineWidth / 2.0))
|
||||
context.addPath(path)
|
||||
context.clip()
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState || transition.previousState is VoiceChatActionButtonBackgroundNodeDisabledState, transition.progress(time: parameters.timestamp) > 0.5 {
|
||||
let progress = (transition.progress(time: parameters.timestamp) - 0.5) / 0.5
|
||||
if let transition = parameters.transition, transition.previousState == .connecting || transition.previousState == .disabled, transition.transition > 0.5 {
|
||||
let progress = (transition.transition - 0.5) / 0.5
|
||||
clearInside = progress
|
||||
}
|
||||
|
||||
@ -798,21 +880,21 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
var iconMuted = true
|
||||
var iconColor: UIColor = .white
|
||||
var backgroundState: VoiceChatActionButtonBackgroundNodeState
|
||||
var backgroundState: VoiceChatActionButtonBackgroundNodeContext
|
||||
switch state {
|
||||
case let .active(state):
|
||||
switch state {
|
||||
case .on:
|
||||
iconMuted = false
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: blobSize, active: true, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobContext(size: blobSize, active: true, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
case .muted:
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: blobSize, active: false, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobContext(size: blobSize, active: false, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
case .cantSpeak:
|
||||
iconColor = UIColor(rgb: 0xff3b30)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeDisabledState()
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeDisabledContext()
|
||||
}
|
||||
case .connecting:
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeConnectingState(blueGradient: self.blueGradient)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeConnectingContext(blueGradient: self.blueGradient)
|
||||
}
|
||||
self.backgroundNode.update(state: backgroundState, simplified: simplified, animated: true)
|
||||
|
||||
@ -871,7 +953,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIBezierPath {
|
||||
extension UIBezierPath {
|
||||
static func smoothCurve(through points: [CGPoint], length: CGFloat, smoothness: CGFloat) -> UIBezierPath {
|
||||
var smoothPoints = [SmoothPoint]()
|
||||
for index in (0 ..< points.count) {
|
||||
|
@ -80,6 +80,10 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatListRecentStatusOnlineHighlightedIcon
|
||||
case chatListRecentStatusOnlinePinnedIcon
|
||||
case chatListRecentStatusOnlinePanelIcon
|
||||
case chatListRecentStatusVoiceChatIcon
|
||||
case chatListRecentStatusVoiceChatHighlightedIcon
|
||||
case chatListRecentStatusVoiceChatPinnedIcon
|
||||
case chatListRecentStatusVoiceChatPanelIcon
|
||||
|
||||
case chatTitleLockIcon
|
||||
case chatTitleMuteIcon
|
||||
|
@ -135,20 +135,21 @@ public struct PresentationResourcesChatList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func recentStatusOnlineIcon(_ theme: PresentationTheme, state: RecentStatusOnlineIconState) -> UIImage? {
|
||||
public static func recentStatusOnlineIcon(_ theme: PresentationTheme, state: RecentStatusOnlineIconState, voiceChat: Bool = false) -> UIImage? {
|
||||
let key: PresentationResourceKey
|
||||
switch state {
|
||||
case .regular:
|
||||
key = PresentationResourceKey.chatListRecentStatusOnlineIcon
|
||||
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatIcon : PresentationResourceKey.chatListRecentStatusOnlineIcon
|
||||
case .highlighted:
|
||||
key = PresentationResourceKey.chatListRecentStatusOnlineHighlightedIcon
|
||||
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatHighlightedIcon : PresentationResourceKey.chatListRecentStatusOnlineHighlightedIcon
|
||||
case .pinned:
|
||||
key = PresentationResourceKey.chatListRecentStatusOnlinePinnedIcon
|
||||
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatPinnedIcon : PresentationResourceKey.chatListRecentStatusOnlinePinnedIcon
|
||||
case .panel:
|
||||
key = PresentationResourceKey.chatListRecentStatusOnlinePanelIcon
|
||||
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatPanelIcon : PresentationResourceKey.chatListRecentStatusOnlinePanelIcon
|
||||
}
|
||||
return theme.image(key.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 14.0, height: 14.0), rotatedContext: { size, context in
|
||||
let size: CGFloat = voiceChat ? 22.0 : 14.0
|
||||
return generateImage(CGSize(width: size, height: size), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
switch state {
|
||||
|
Loading…
x
Reference in New Issue
Block a user