mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '2e71f2f0df493936c728f08cd8eb6c6815109e45'
This commit is contained in:
commit
b0d022e897
@ -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)
|
let offset: CGFloat = sideInset.isZero ? 0.0 : initialBottomInset + 8.0
|
||||||
if let image = self.pinButtonIconNode.image {
|
let pinButtonSize = self.pinButtonNode.update(size: size, transition: transition)
|
||||||
let offset: CGFloat = sideInset.isZero ? 0.0 : initialBottomInset + 8.0
|
transition.updateFrame(node: self.pinButtonNode, frame: CGRect(origin: CGPoint(x: size.width - pinButtonSize.width - offset, y: 0.0), size: pinButtonSize))
|
||||||
transition.updateFrame(node: self.pinButtonIconNode, frame: CGRect(origin: CGPoint(x: size.width - image.size.width - offset, y: 0.0), size: image.size))
|
|
||||||
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(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)))
|
||||||
|
|
||||||
|
201
submodules/TelegramCallsUI/Sources/VoiceChatPinNode.swift
Normal file
201
submodules/TelegramCallsUI/Sources/VoiceChatPinNode.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user