Swiftgram/TelegramUI/ChatMessageDateHeader.swift
2017-01-31 14:43:03 +03:00

186 lines
6.7 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
private let timezoneOffset: Int = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
return Int(timeinfoNow.tm_gmtoff)
}()
private let granularity: Int32 = 60 * 60 * 24
//private let granularity: Int32 = 60 * 60
final class ChatMessageDateHeader: ListViewItemHeader {
private let timestamp: Int32
private let roundedTimestamp: Int32
let id: Int64
init(timestamp: Int32) {
self.timestamp = timestamp
if timestamp == Int32.max {
self.roundedTimestamp = timestamp / (granularity) * (granularity)
} else {
self.roundedTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
}
self.id = Int64(self.roundedTimestamp)
}
let stickDirection: ListViewItemHeaderStickDirection = .bottom
let height: CGFloat = 34.0
func node() -> ListViewItemHeaderNode {
return ChatMessageDateHeaderNode(timestamp: self.roundedTimestamp)
}
}
private func backgroundImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 13)
}
private let titleFont = Font.medium(13.0)
private let months: [String] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
let labelNode: TextNode
let backgroundNode: ASImageNode
let stickBackgroundNode: ASImageNode
private let timestamp: Int32
private var flashingOnScrolling = false
private var stickDistanceFactor: CGFloat = 0.0
init(timestamp: Int32) {
self.timestamp = timestamp
self.labelNode = TextNode()
self.labelNode.isLayerBacked = true
self.labelNode.displaysAsynchronously = true
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.stickBackgroundNode = ASImageNode()
self.stickBackgroundNode.isLayerBacked = true
self.stickBackgroundNode.displayWithoutProcessing = true
self.stickBackgroundNode.displaysAsynchronously = false
super.init(dynamicBounce: true, isRotated: true)
self.isLayerBacked = true
self.transform = CATransform3DMakeRotation(CGFloat(M_PI), 0.0, 0.0, 1.0)
self.backgroundNode.image = backgroundImage(color: UIColor(0x748391, 0.45))
self.stickBackgroundNode.image = backgroundImage(color: UIColor(0x939fab, 0.5))
self.stickBackgroundNode.alpha = 0.0
self.backgroundNode.addSubnode(self.stickBackgroundNode)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.labelNode)
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(timestamp)
var timeinfo: tm = tm()
localtime_r(&t, &timeinfo)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
let text: String
if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday {
text = "Today"
} else {
text = "\(months[Int(timeinfo.tm_mon)]) \(timeinfo.tm_mday)"
}
let attributedString = NSAttributedString(string: text, font: titleFont, textColor: UIColor.white)
let labelLayout = TextNode.asyncLayout(self.labelNode)
let (size, apply) = labelLayout(attributedString, nil, 1, .end, CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), nil)
apply()
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
}
override func layout() {
super.layout()
let bounds = self.bounds
let labelLayout = TextNode.asyncLayout(self.labelNode)
let size = self.labelNode.bounds.size
let backgroundSize = CGSize(width: size.width + 8.0 + 8.0, height: 26.0)
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.size.width - backgroundSize.width) / 2.0), y: (34.0 - 26.0) / 2.0), size: backgroundSize)
self.stickBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
self.backgroundNode.frame = backgroundFrame
self.labelNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + 8.0, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - size.height) / 2.0) - 1.0), size: size)
}
override func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
if !self.stickDistanceFactor.isEqual(to: factor) {
self.stickBackgroundNode.alpha = factor
let wasZero = self.stickDistanceFactor < 0.5
let isZero = factor < 0.5
self.stickDistanceFactor = factor
if wasZero != isZero {
var animated = true
if case .immediate = transition {
animated = false
}
self.updateFlashing(animated: animated)
}
}
}
override func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
self.flashingOnScrolling = isFlashingOnScrolling
self.updateFlashing(animated: animated)
}
private func updateFlashing(animated: Bool) {
let flashing = self.flashingOnScrolling || self.stickDistanceFactor < 0.5
let alpha: CGFloat = flashing ? 1.0 : 0.0
let previousAlpha = self.backgroundNode.alpha
if !previousAlpha.isEqual(to: alpha) {
self.backgroundNode.alpha = alpha
self.labelNode.alpha = alpha
if animated {
let duration: Double = flashing ? 0.3 : 0.4
self.backgroundNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
self.labelNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
}
}
}
}