mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Cmd+# shortcuts to navigate between chats Changed day theme service messages background color to match current wallpaper when there is one
242 lines
9.5 KiB
Swift
242 lines
9.5 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
|
|
|
|
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)
|
|
|
|
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: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper))
|
|
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)
|
|
}
|
|
|
|
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 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 {
|
|
self.action?(self.localTimestamp)
|
|
}
|
|
}
|
|
}
|