Swiftgram/TelegramUI/ChatMessageDateHeader.swift

262 lines
10 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
private let timezoneOffset: Int32 = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
return Int32(timeinfoNow.tm_gmtoff)
}()
private let granularity: Int32 = 60 * 60 * 24
final class ChatMessageDateHeader: ListViewItemHeader {
private let timestamp: Int32
private let roundedTimestamp: Int32
let id: Int64
let presentationData: ChatPresentationData
let action: ((Int32) -> Void)?
init(timestamp: Int32, presentationData: ChatPresentationData, action:((Int32) -> Void)? = nil) {
self.timestamp = timestamp
self.presentationData = presentationData
self.action = action
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(localTimestamp: self.roundedTimestamp, presentationData: self.presentationData, action: self.action)
}
}
private let titleFont = Font.medium(13.0)
private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String {
switch index {
case 0:
return strings.Month_GenJanuary
case 1:
return strings.Month_GenFebruary
case 2:
return strings.Month_GenMarch
case 3:
return strings.Month_GenApril
case 4:
return strings.Month_GenMay
case 5:
return strings.Month_GenJune
case 6:
return strings.Month_GenJuly
case 7:
return strings.Month_GenAugust
case 8:
return strings.Month_GenSeptember
case 9:
return strings.Month_GenOctober
case 10:
return strings.Month_GenNovember
case 11:
return strings.Month_GenDecember
default:
return ""
}
}
final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
let labelNode: TextNode
let backgroundNode: ASImageNode
let stickBackgroundNode: ASImageNode
private let localTimestamp: Int32
private var presentationData: ChatPresentationData
private var flashingOnScrolling = false
private var stickDistanceFactor: CGFloat = 0.0
private var action:((Int32) -> Void)? = nil
init(localTimestamp: Int32, presentationData: ChatPresentationData, action:((Int32) -> Void)? = nil) {
self.presentationData = presentationData
self.localTimestamp = localTimestamp
self.action = action
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
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
//self.testNode.backgroundColor = .black
//self.testNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: true, isRotated: true, seeThrough: false)
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
self.backgroundNode.image = graphics.dateStaticBackground
self.stickBackgroundNode.image = graphics.dateFloatingBackground
self.stickBackgroundNode.alpha = 0.0
self.backgroundNode.addSubnode(self.stickBackgroundNode)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.labelNode)
//self.addSubnode(self.testNode)
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(localTimestamp)
var timeinfo: tm = tm()
gmtime_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 {
if timeinfo.tm_yday == timeinfoNow.tm_yday {
text = presentationData.strings.Weekday_Today
} else {
text = presentationData.strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: presentationData.strings), "\(timeinfo.tm_mday)").0
}
} else {
text = presentationData.strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: presentationData.strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").0
}
let attributedString = NSAttributedString(string: text, font: titleFont, textColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor)
let labelLayout = TextNode.asyncLayout(self.labelNode)
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = apply()
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
/*(self.layer as! CASeeThroughTracingLayer).updateRelativePosition = { [weak self] position in
if let strongSelf = self {
strongSelf.testNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 70.0 + position.y), size: CGSize(width: 40.0, height: 20.0))
print("position \(position.x), \(position.y)")
}
}*/
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
func updatePresentationData(_ presentationData: ChatPresentationData) {
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
self.backgroundNode.image = graphics.dateStaticBackground
self.stickBackgroundNode.image = graphics.dateFloatingBackground
self.setNeedsLayout()
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
//let labelLayout = TextNode.asyncLayout(self.labelNode)
let chatDateSize: CGFloat = 20.0
let chatDateInset: CGFloat = 6.0
let labelSize = self.labelNode.bounds.size
let backgroundSize = CGSize(width: labelSize.width + chatDateInset * 2.0, height: chatDateSize)
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: (34.0 - chatDateSize) / 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 + chatDateInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - labelSize.height) / 2.0)), size: labelSize)
}
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)
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.bounds.contains(point) {
return nil
}
if self.labelNode.alpha.isZero {
return nil
}
if self.backgroundNode.frame.contains(point) {
return self.view
}
return nil
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
}
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
if case .ended = recognizer.state {
action?(self.localTimestamp)
}
}
}