mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Various Improvements
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import LegacyComponents
|
||||
import TelegramPresentationData
|
||||
import TooltipUI
|
||||
|
||||
private final class ChecksNodeParameters: NSObject {
|
||||
let color: UIColor
|
||||
let progress: CGFloat
|
||||
|
||||
init(color: UIColor, progress: CGFloat) {
|
||||
self.color = color
|
||||
self.progress = progress
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
private class ChecksNode: ASDisplayNode {
|
||||
var state: Bool? = nil
|
||||
|
||||
var color: UIColor {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var effectiveProgress: CGFloat = 1.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .clear
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
func animateProgress(from: CGFloat, to: CGFloat) {
|
||||
self.pop_removeAllAnimations()
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! ChecksNode).effectiveProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! ChecksNode).effectiveProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as! POPAnimatableProperty)
|
||||
animation.fromValue = from as NSNumber
|
||||
animation.toValue = to as NSNumber
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.duration = 0.2
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return ChecksNodeParameters(color: self.color, progress: self.effectiveProgress)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
}
|
||||
|
||||
@objc override 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? ChecksNodeParameters else {
|
||||
return
|
||||
}
|
||||
|
||||
let scaleFactor: CGFloat = 1.0
|
||||
context.translateBy(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||
context.scaleBy(x: scaleFactor, y: scaleFactor)
|
||||
context.translateBy(x: -bounds.width / 2.0, y: -bounds.height / 2.0)
|
||||
|
||||
let progress = parameters.progress
|
||||
|
||||
context.setStrokeColor(parameters.color.cgColor)
|
||||
context.setLineWidth(1.0 + UIScreenPixel)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
|
||||
context.saveGState()
|
||||
var s1 = CGPoint(x: 9.0, y: 13.0)
|
||||
var s2 = CGPoint(x: 5.0, y: 13.0)
|
||||
let p1 = CGPoint(x: 3.5, y: 3.5)
|
||||
let p2 = CGPoint(x: 7.5 - UIScreenPixel, y: -8.0)
|
||||
|
||||
let check1FirstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0))
|
||||
let check2FirstSegment: CGFloat = max(0.0, min(1.0, (progress - 1.0) * 3.0))
|
||||
|
||||
let firstProgress = max(0.0, min(1.0, progress))
|
||||
let secondProgress = max(0.0, min(1.0, progress - 1.0))
|
||||
|
||||
let scale: CGFloat = 1.2
|
||||
context.translateBy(x: 16.0, y: 13.0)
|
||||
context.scaleBy(x: scale - abs((scale - 1.0) * (firstProgress - 0.5) / 0.5), y: scale - abs((scale - 1.0) * (firstProgress - 0.5) / 0.5))
|
||||
s1 = s1.offsetBy(dx: -16.0, dy: -13.0)
|
||||
|
||||
if !check1FirstSegment.isZero {
|
||||
if check1FirstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s1.x + p1.x * check1FirstSegment, y: s1.y + p1.y * check1FirstSegment))
|
||||
context.addLine(to: s1)
|
||||
} else {
|
||||
let secondSegment = (min(1.0, progress) - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s1.x + p1.x + p2.x * secondSegment, y: s1.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s1.x + p1.x, y: s1.y + p1.y))
|
||||
context.addLine(to: CGPoint(x: s1.x + p1.x * min(1.0, check2FirstSegment), y: s1.y + p1.y * min(1.0, check2FirstSegment)))
|
||||
}
|
||||
}
|
||||
context.strokePath()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.translateBy(x: 12.0, y: 13.0)
|
||||
context.scaleBy(x: scale - abs((scale - 1.0) * (secondProgress - 0.5) / 0.5), y: scale - abs((scale - 1.0) * (secondProgress - 0.5) / 0.5))
|
||||
s2 = s2.offsetBy(dx: -12.0, dy: -13.0)
|
||||
|
||||
if !check2FirstSegment.isZero {
|
||||
if check2FirstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s2.x + p1.x * check2FirstSegment, y: s2.y + p1.y * check2FirstSegment))
|
||||
context.addLine(to: s2)
|
||||
} else {
|
||||
let secondSegment = (max(0.0, (progress - 1.0)) - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s2.x + p1.x + p2.x * secondSegment, y: s2.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s2.x + p1.x, y: s2.y + p1.y))
|
||||
context.addLine(to: s2)
|
||||
}
|
||||
}
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
func updateState(_ state: Bool, animated: Bool) {
|
||||
guard state != self.state else {
|
||||
return
|
||||
}
|
||||
let previousState = self.state
|
||||
self.state = state
|
||||
if animated {
|
||||
if previousState == nil && self.state == false {
|
||||
self.animateProgress(from: 0.0, to: 1.0)
|
||||
} else if previousState == false && self.state == true {
|
||||
self.animateProgress(from: 1.0, to: 2.0)
|
||||
}
|
||||
} else {
|
||||
if let state = self.state {
|
||||
self.effectiveProgress = state ? 2.0 : 1.0
|
||||
} else {
|
||||
self.effectiveProgress = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChatStatusChecksTooltipContentNode: ASDisplayNode, TooltipControllerCustomContentNode {
|
||||
private let deliveredChecksNode: ChecksNode
|
||||
private let deliveredTextNode: ImmediateTextNode
|
||||
private let readChecksNode: ChecksNode
|
||||
private let readTextNode: ImmediateTextNode
|
||||
|
||||
init(presentationData: PresentationData) {
|
||||
self.deliveredChecksNode = ChecksNode(color: .white)
|
||||
self.deliveredTextNode = ImmediateTextNode()
|
||||
self.readChecksNode = ChecksNode(color: .white)
|
||||
self.readTextNode = ImmediateTextNode()
|
||||
|
||||
self.deliveredTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ChecksTooltip_Delivered, font: Font.regular(14.0), textColor: UIColor.white)
|
||||
self.readTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ChecksTooltip_Read, font: Font.regular(14.0), textColor: UIColor.white)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.deliveredChecksNode)
|
||||
self.addSubnode(self.deliveredTextNode)
|
||||
self.addSubnode(self.readChecksNode)
|
||||
self.addSubnode(self.readTextNode)
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.deliveredChecksNode.updateState(false, animated: true)
|
||||
self.readChecksNode.updateState(false, animated: true)
|
||||
|
||||
self.deliveredChecksNode.layer.animateScale(from: 1.0, to: 1.12, duration: 0.25, delay: 0.1, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.deliveredChecksNode.layer.animateScale(from: 1.12, to: 1.0, duration: 0.25)
|
||||
}
|
||||
})
|
||||
|
||||
self.deliveredTextNode.layer.animateScale(from: 1.0, to: 1.12, duration: 0.25, delay: 0.1, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.deliveredTextNode.layer.animateScale(from: 1.12, to: 1.0, duration: 0.25)
|
||||
}
|
||||
})
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
self.readChecksNode.updateState(true, animated: true)
|
||||
|
||||
self.readChecksNode.layer.animateScale(from: 1.0, to: 1.12, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.readChecksNode.layer.animateScale(from: 1.12, to: 1.0, duration: 0.25)
|
||||
}
|
||||
})
|
||||
|
||||
self.readTextNode.layer.animateScale(from: 1.0, to: 1.12, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.readTextNode.layer.animateScale(from: 1.12, to: 1.0, duration: 0.25)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) -> CGSize {
|
||||
let deliveredSize = self.deliveredTextNode.updateLayout(size)
|
||||
let readSize = self.readTextNode.updateLayout(size)
|
||||
|
||||
let checksInset: CGFloat = 8.0
|
||||
let checksSize = CGSize(width: 24.0, height: 24.0)
|
||||
|
||||
self.deliveredChecksNode.frame = CGRect(origin: CGPoint(x: checksInset, y: 15.0), size: checksSize)
|
||||
self.deliveredTextNode.frame = CGRect(origin: CGPoint(x: checksInset + checksSize.width + 5.0, y: 19.0), size: deliveredSize)
|
||||
self.readChecksNode.frame = CGRect(origin: CGPoint(x: checksInset, y: 38.0), size: checksSize)
|
||||
self.readTextNode.frame = CGRect(origin: CGPoint(x: checksInset + checksSize.width + 5.0, y: 43.0), size: readSize)
|
||||
|
||||
let contentWidth = max(deliveredSize.width, readSize.width) + checksInset + checksSize.width + 18.0
|
||||
let contentHeight: CGFloat = 77.0
|
||||
|
||||
return CGSize(width: contentWidth, height: contentHeight)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user