mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Temp
This commit is contained in:
297
submodules/TelegramUI/Sources/ChatMessageDateHeader.swift
Normal file
297
submodules/TelegramUI/Sources/ChatMessageDateHeader.swift
Normal file
@@ -0,0 +1,297 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import Postbox
|
||||
import SyncCore
|
||||
import AccountContext
|
||||
|
||||
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
|
||||
private let scheduled: Bool
|
||||
|
||||
let id: Int64
|
||||
let presentationData: ChatPresentationData
|
||||
let context: AccountContext
|
||||
let action: ((Int32) -> Void)?
|
||||
|
||||
init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, context: AccountContext, action: ((Int32) -> Void)? = nil) {
|
||||
self.timestamp = timestamp
|
||||
self.scheduled = scheduled
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
self.action = action
|
||||
if timestamp == scheduleWhenOnlineTimestamp {
|
||||
self.roundedTimestamp = scheduleWhenOnlineTimestamp
|
||||
} else 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, scheduled: self.scheduled, presentationData: self.presentationData, context: self.context, action: self.action)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
|
||||
guard let node = node as? ChatMessageDateHeaderNode, let next = next as? ChatMessageDateHeader else {
|
||||
return
|
||||
}
|
||||
node.updatePresentationData(next.presentationData, context: next.context)
|
||||
}
|
||||
}
|
||||
|
||||
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 let context: AccountContext
|
||||
private let text: String
|
||||
|
||||
private var flashingOnScrolling = false
|
||||
private var stickDistanceFactor: CGFloat = 0.0
|
||||
private var action: ((Int32) -> Void)? = nil
|
||||
|
||||
init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, context: AccountContext, action: ((Int32) -> Void)? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
|
||||
self.localTimestamp = localTimestamp
|
||||
self.action = action
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
self.labelNode.displaysAsynchronously = !presentationData.isPreview
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
var 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
|
||||
}
|
||||
|
||||
if scheduled {
|
||||
if localTimestamp == scheduleWhenOnlineTimestamp {
|
||||
text = presentationData.strings.ScheduledMessages_ScheduledOnline
|
||||
} else if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday {
|
||||
text = presentationData.strings.ScheduledMessages_ScheduledToday
|
||||
} else {
|
||||
text = presentationData.strings.ScheduledMessages_ScheduledDate(text).0
|
||||
}
|
||||
}
|
||||
self.text = text
|
||||
|
||||
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(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||
|
||||
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 titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.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, context: AccountContext) {
|
||||
let previousPresentationData = self.presentationData
|
||||
self.presentationData = presentationData
|
||||
|
||||
let graphics = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||
|
||||
self.backgroundNode.image = graphics.dateStaticBackground
|
||||
self.stickBackgroundNode.image = graphics.dateFloatingBackground
|
||||
|
||||
let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)))
|
||||
|
||||
let attributedString = NSAttributedString(string: self.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()
|
||||
|
||||
if presentationData.fontSize != previousPresentationData.fontSize {
|
||||
self.labelNode.bounds = CGRect(origin: CGPoint(), size: size.size)
|
||||
}
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
func updateBackgroundColor(_ color: UIColor) {
|
||||
let chatDateSize: CGFloat = 20.0
|
||||
self.backgroundNode.image = generateImage(CGSize(width: chatDateSize, height: chatDateSize), 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: Int(chatDateSize) / 2, topCapHeight: Int(chatDateSize) / 2)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user