Merge commit '2e71f2f0df493936c728f08cd8eb6c6815109e45'

This commit is contained in:
Ali 2021-06-11 15:20:14 +04:00
commit b0d022e897
2 changed files with 275 additions and 38 deletions

View File

@ -26,6 +26,74 @@ private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
private let fadeHeight: CGFloat = 50.0 private let fadeHeight: CGFloat = 50.0
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
private class VoiceChatPinButtonNode: HighlightTrackingButtonNode {
private let pinButtonIconNode: VoiceChatPinNode
private let pinButtonClippingnode: ASDisplayNode
private let pinButtonTitleNode: ImmediateTextNode
init(presentationData: PresentationData) {
self.pinButtonIconNode = VoiceChatPinNode()
self.pinButtonClippingnode = ASDisplayNode()
self.pinButtonClippingnode.clipsToBounds = true
self.pinButtonTitleNode = ImmediateTextNode()
self.pinButtonTitleNode.attributedText = NSAttributedString(string: presentationData.strings.VoiceChat_Unpin, font: Font.regular(17.0), textColor: .white)
self.pinButtonTitleNode.alpha = 0.0
super.init()
self.addSubnode(self.pinButtonClippingnode)
self.addSubnode(self.pinButtonIconNode)
self.pinButtonClippingnode.addSubnode(self.pinButtonTitleNode)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.pinButtonClippingnode.layer.removeAnimation(forKey: "opacity")
strongSelf.pinButtonIconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.pinButtonClippingnode.alpha = 0.4
strongSelf.pinButtonIconNode.alpha = 0.4
} else {
strongSelf.pinButtonClippingnode.alpha = 1.0
strongSelf.pinButtonIconNode.alpha = 1.0
strongSelf.pinButtonClippingnode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.pinButtonIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
private var isPinned = false
func update(pinned: Bool, animated: Bool) {
let wasPinned = self.isPinned
self.pinButtonIconNode.update(state: .init(pinned: pinned, color: .white), animated: true)
self.isPinned = pinned
self.pinButtonTitleNode.alpha = self.isPinned ? 1.0 : 0.0
if animated {
if wasPinned {
self.pinButtonTitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.pinButtonTitleNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.pinButtonTitleNode.frame.width, y: 0.0), duration: 0.2, additive: true)
} else {
self.pinButtonTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.pinButtonTitleNode.layer.animatePosition(from: CGPoint(x: self.pinButtonTitleNode.frame.width, y: 0.0), to: CGPoint(), duration: 0.2, additive: true)
}
}
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let unpinSize = self.pinButtonTitleNode.updateLayout(size)
let pinIconSize = CGSize(width: 48.0, height: 48.0)
let totalSize = CGSize(width: unpinSize.width + pinIconSize.width, height: 44.0)
transition.updateFrame(node: self.pinButtonIconNode, frame: CGRect(origin: CGPoint(x: totalSize.width - pinIconSize.width, y: 0.0), size: pinIconSize))
transition.updateFrame(node: self.pinButtonTitleNode, frame: CGRect(origin: CGPoint(x: 4.0, y: 12.0), size: unpinSize))
transition.updateFrame(node: self.pinButtonClippingnode, frame: CGRect(x: 0.0, y: 0.0, width: totalSize.width - pinIconSize.width * 0.6667, height: 44.0))
return totalSize
}
}
final class VoiceChatMainStageNode: ASDisplayNode { final class VoiceChatMainStageNode: ASDisplayNode {
private let context: AccountContext private let context: AccountContext
private let call: PresentationGroupCall private let call: PresentationGroupCall
@ -44,9 +112,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
private let headerNode: ASDisplayNode private let headerNode: ASDisplayNode
private let backButtonNode: HighlightableButtonNode private let backButtonNode: HighlightableButtonNode
private let backButtonArrowNode: ASImageNode private let backButtonArrowNode: ASImageNode
private let pinButtonNode: HighlightTrackingButtonNode private let pinButtonNode: VoiceChatPinButtonNode
private let pinButtonIconNode: ASImageNode
private let pinButtonTitleNode: ImmediateTextNode
private let audioLevelNode: VoiceChatBlobNode private let audioLevelNode: VoiceChatBlobNode
private let audioLevelDisposable = MetaDisposable() private let audioLevelDisposable = MetaDisposable()
private let speakingPeerDisposable = MetaDisposable() private let speakingPeerDisposable = MetaDisposable()
@ -136,14 +202,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.pinButtonIconNode = ASImageNode() self.pinButtonNode = VoiceChatPinButtonNode(presentationData: presentationData)
self.pinButtonIconNode.displayWithoutProcessing = true
self.pinButtonIconNode.displaysAsynchronously = false
self.pinButtonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Pin"), color: .white)
self.pinButtonTitleNode = ImmediateTextNode()
self.pinButtonTitleNode.isHidden = true
self.pinButtonTitleNode.attributedText = NSAttributedString(string: presentationData.strings.VoiceChat_Unpin, font: Font.regular(17.0), textColor: .white)
self.pinButtonNode = HighlightableButtonNode()
self.backdropAvatarNode = ImageNode() self.backdropAvatarNode = ImageNode()
self.backdropAvatarNode.contentMode = .scaleAspectFill self.backdropAvatarNode.contentMode = .scaleAspectFill
@ -212,8 +271,6 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.addSubnode(self.headerNode) self.addSubnode(self.headerNode)
self.headerNode.addSubnode(self.backButtonNode) self.headerNode.addSubnode(self.backButtonNode)
self.headerNode.addSubnode(self.backButtonArrowNode) self.headerNode.addSubnode(self.backButtonArrowNode)
self.headerNode.addSubnode(self.pinButtonIconNode)
self.headerNode.addSubnode(self.pinButtonTitleNode)
self.headerNode.addSubnode(self.pinButtonNode) self.headerNode.addSubnode(self.pinButtonNode)
self.addSubnode(self.placeholderIconNode) self.addSubnode(self.placeholderIconNode)
@ -257,22 +314,6 @@ final class VoiceChatMainStageNode: ASDisplayNode {
} }
} }
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside) self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
self.pinButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.pinButtonTitleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.pinButtonIconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.pinButtonTitleNode.alpha = 0.4
strongSelf.pinButtonIconNode.alpha = 0.4
} else {
strongSelf.pinButtonTitleNode.alpha = 1.0
strongSelf.pinButtonIconNode.alpha = 1.0
strongSelf.pinButtonTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.pinButtonIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.pinButtonNode.addTarget(self, action: #selector(self.pinPressed), forControlEvents: .touchUpInside) self.pinButtonNode.addTarget(self, action: #selector(self.pinPressed), forControlEvents: .touchUpInside)
} }
@ -727,8 +768,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, isTablet: isTablet, transition: .immediate) self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, isTablet: isTablet, transition: .immediate)
} }
self.pinButtonTitleNode.isHidden = !pinned self.pinButtonNode.update(pinned: pinned, animated: true)
self.pinButtonIconNode.image = !pinned ? generateTintedImage(image: UIImage(bundleImageName: "Call/Pin"), color: .white) : generateTintedImage(image: UIImage(bundleImageName: "Call/Unpin"), color: .white)
self.audioLevelNode.startAnimating(immediately: true) self.audioLevelNode.startAnimating(immediately: true)
@ -1091,13 +1131,9 @@ final class VoiceChatMainStageNode: ASDisplayNode {
} }
transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: sideInset + 27.0, y: 12.0), size: backSize)) transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: sideInset + 27.0, y: 12.0), size: backSize))
let unpinSize = self.pinButtonTitleNode.updateLayout(size)
if let image = self.pinButtonIconNode.image {
let offset: CGFloat = sideInset.isZero ? 0.0 : initialBottomInset + 8.0 let offset: CGFloat = sideInset.isZero ? 0.0 : initialBottomInset + 8.0
transition.updateFrame(node: self.pinButtonIconNode, frame: CGRect(origin: CGPoint(x: size.width - image.size.width - offset, y: 0.0), size: image.size)) let pinButtonSize = self.pinButtonNode.update(size: size, transition: transition)
transition.updateFrame(node: self.pinButtonTitleNode, frame: CGRect(origin: CGPoint(x: size.width - image.size.width - unpinSize.width + 4.0 - offset, y: 12.0), size: unpinSize)) transition.updateFrame(node: self.pinButtonNode, frame: CGRect(origin: CGPoint(x: size.width - pinButtonSize.width - offset, y: 0.0), size: pinButtonSize))
transition.updateFrame(node: self.pinButtonNode, frame: CGRect(x: size.width - image.size.width - unpinSize.width - offset, y: 0.0, width: unpinSize.width + image.size.width, height: 44.0))
}
transition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 64.0))) transition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 64.0)))

View File

@ -0,0 +1,201 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private let iconImage = generateTintedImage(image: UIImage(bundleImageName: "Call/Pin"), color: .white)
private final class VoiceChatPinNodeDrawingState: NSObject {
let color: UIColor
let transition: CGFloat
let reverse: Bool
init(color: UIColor, transition: CGFloat, reverse: Bool) {
self.color = color
self.transition = transition
self.reverse = reverse
super.init()
}
}
final class VoiceChatPinNode: ASDisplayNode {
class State: Equatable {
let pinned: Bool
let color: UIColor
init(pinned: Bool, color: UIColor) {
self.pinned = pinned
self.color = color
}
static func ==(lhs: State, rhs: State) -> Bool {
if lhs.pinned != rhs.pinned {
return false
}
if lhs.color.argb != rhs.color.argb {
return false
}
return true
}
}
private class TransitionContext {
let startTime: Double
let duration: Double
let previousState: State
init(startTime: Double, duration: Double, previousState: State) {
self.startTime = startTime
self.duration = duration
self.previousState = previousState
}
}
private var animator: ConstantDisplayLinkAnimator?
private var hasState = false
private var state: State = State(pinned: false, color: .black)
private var transitionContext: TransitionContext?
override init() {
super.init()
self.isOpaque = false
}
func update(state: State, animated: Bool) {
var animated = animated
if !self.hasState {
self.hasState = true
animated = false
}
if self.state != state {
let previousState = self.state
self.state = state
if animated {
self.transitionContext = TransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousState: previousState)
}
self.updateAnimations()
self.setNeedsDisplay()
}
}
private func updateAnimations() {
var animate = false
let timestamp = CACurrentMediaTime()
if let transitionContext = self.transitionContext {
if transitionContext.startTime + transitionContext.duration < timestamp {
self.transitionContext = nil
} else {
animate = true
}
}
if animate {
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations()
})
self.animator = animator
}
animator.isPaused = false
} else {
self.animator?.isPaused = true
}
self.setNeedsDisplay()
}
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
var transitionFraction: CGFloat = self.state.pinned ? 1.0 : 0.0
var color = self.state.color
var reverse = false
if let transitionContext = self.transitionContext {
let timestamp = CACurrentMediaTime()
var t = CGFloat((timestamp - transitionContext.startTime) / transitionContext.duration)
t = min(1.0, max(0.0, t))
if transitionContext.previousState.pinned != self.state.pinned {
transitionFraction = self.state.pinned ? t : 1.0 - t
reverse = transitionContext.previousState.pinned
}
if transitionContext.previousState.color.rgb != color.rgb {
color = transitionContext.previousState.color.interpolateTo(color, fraction: t)!
}
}
return VoiceChatPinNodeDrawingState(color: color, transition: transitionFraction, reverse: reverse)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()!
if !isRasterizing {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fill(bounds)
}
guard let parameters = parameters as? VoiceChatPinNodeDrawingState else {
return
}
context.setFillColor(parameters.color.cgColor)
let clearLineWidth: CGFloat = 2.0
let lineWidth: CGFloat = 1.0 + UIScreenPixel
if let iconImage = iconImage?.cgImage {
context.saveGState()
context.translateBy(x: bounds.midX, y: bounds.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -bounds.midX, y: -bounds.midY)
context.draw(iconImage, in: CGRect(origin: CGPoint(), size: CGSize(width: 48.0, height: 48.0)))
context.restoreGState()
}
if parameters.transition > 0.0 {
let startPoint: CGPoint
let endPoint: CGPoint
let origin = CGPoint(x: 14.0, y: 16.0 - UIScreenPixel)
let length: CGFloat = 17.0
if parameters.reverse {
startPoint = CGPoint(x: origin.x + length * (1.0 - parameters.transition), y: origin.y + length * (1.0 - parameters.transition)).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
endPoint = CGPoint(x: origin.x + length, y: origin.y + length).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
} else {
startPoint = origin.offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
endPoint = CGPoint(x: origin.x + length * parameters.transition, y: origin.y + length * parameters.transition).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
}
context.setBlendMode(.clear)
context.setLineWidth(clearLineWidth)
context.move(to: startPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
context.addLine(to: endPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
context.strokePath()
context.setBlendMode(.normal)
context.setStrokeColor(parameters.color.cgColor)
context.setLineWidth(lineWidth)
context.setLineCap(.round)
context.setLineJoin(.round)
context.move(to: startPoint)
context.addLine(to: endPoint)
context.strokePath()
}
}
}