mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-25 15:37:36 +00:00
no message
This commit is contained in:
@@ -12,11 +12,14 @@ private struct FetchControls {
|
||||
|
||||
private let titleFont = Font.regular(16.0)
|
||||
private let descriptionFont = Font.regular(13.0)
|
||||
private let durationFont = Font.regular(11.0)
|
||||
|
||||
private let incomingTitleColor = UIColor(0x0b8bed)
|
||||
private let outgoingTitleColor = UIColor(0x3faa3c)
|
||||
private let incomingDescriptionColor = UIColor(0x999999)
|
||||
private let outgoingDescriptionColor = UIColor(0x6fb26a)
|
||||
private let incomingDurationColor = UIColor(0x525252, 0.6)
|
||||
private let outgoingDurationColor = UIColor(0x008c09, 0.8)
|
||||
|
||||
private let fileIconIncomingImage = UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentIncoming")?.precomposed()
|
||||
private let fileIconOutgoingImage = UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentOutgoing")?.precomposed()
|
||||
@@ -24,6 +27,8 @@ private let fileIconOutgoingImage = UIImage(bundleImageName: "Chat/Message/Radia
|
||||
final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
private let titleNode: TextNode
|
||||
private let descriptionNode: TextNode
|
||||
private let waveformNode: AudioWaveformNode
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
|
||||
private var iconNode: TransformImageNode?
|
||||
private var progressNode: RadialProgressNode?
|
||||
@@ -48,6 +53,10 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
self.descriptionNode.displaysAsynchronously = true
|
||||
self.descriptionNode.isLayerBacked = true
|
||||
|
||||
self.waveformNode = AudioWaveformNode()
|
||||
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
@@ -92,14 +101,15 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) {
|
||||
func asyncLayout() -> (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) {
|
||||
let currentFile = self.file
|
||||
|
||||
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let descriptionAsyncLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
let currentMessageIdAndFlags = self.messageIdAndFlags
|
||||
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
||||
|
||||
return { account, message, file, incoming, constrainedSize in
|
||||
return { account, message, file, incoming, dateAndStatusType, constrainedSize in
|
||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||
//var updateImageSignal: Signal<TransformImageArguments -> DrawingContext, NoError>?
|
||||
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
||||
@@ -128,47 +138,102 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
}
|
||||
|
||||
if statusUpdated {
|
||||
updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(message.id))
|
||||
|> map { resourceStatus, pendingStatus -> MediaResourceStatus in
|
||||
if let pendingStatus = pendingStatus {
|
||||
return .Fetching(progress: pendingStatus.progress)
|
||||
} else {
|
||||
return resourceStatus
|
||||
if message.flags.contains(.Unsent) && !message.flags.contains(.Failed) {
|
||||
updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(message.id))
|
||||
|> map { resourceStatus, pendingStatus -> MediaResourceStatus in
|
||||
if let pendingStatus = pendingStatus {
|
||||
return .Fetching(progress: pendingStatus.progress)
|
||||
} else {
|
||||
return resourceStatus
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updatedStatusSignal = chatMessageFileStatus(account: account, file: file)
|
||||
}
|
||||
}
|
||||
|
||||
var statusSize: CGSize?
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = dateAndStatusType {
|
||||
var t = Int(message.timestamp)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&t, &timeinfo)
|
||||
|
||||
var edited = false
|
||||
var viewCount: Int?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
edited = true
|
||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||
viewCount = attribute.count
|
||||
}
|
||||
}
|
||||
var dateText = String(format: "%02d:%02d", arguments: [Int(timeinfo.tm_hour), Int(timeinfo.tm_min)])
|
||||
if let viewCount = viewCount {
|
||||
dateText = "\(viewCount) " + dateText
|
||||
}
|
||||
if edited {
|
||||
dateText = "edited " + dateText
|
||||
}
|
||||
|
||||
let (size, apply) = statusLayout(dateText, statusType, constrainedSize)
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
var candidateTitleString: NSAttributedString?
|
||||
var candidateDescriptionString: NSAttributedString?
|
||||
|
||||
var isAudio = false
|
||||
var audioWaveform: AudioWaveform?
|
||||
var isVoice = false
|
||||
var audioDuration: Int32 = 0
|
||||
|
||||
for attribute in file.attributes {
|
||||
if case let .Audio(_, _, title, performer, _) = attribute {
|
||||
candidateTitleString = NSAttributedString(string: title ?? "Unknown Track", font: titleFont, textColor: incoming ? incomingTitleColor : outgoingTitleColor)
|
||||
let descriptionText: String
|
||||
if let performer = performer {
|
||||
descriptionText = performer
|
||||
} else if let size = file.size {
|
||||
descriptionText = dataSizeString(size)
|
||||
} else {
|
||||
descriptionText = ""
|
||||
if case let .Audio(voice, duration, title, performer, waveform) = attribute {
|
||||
isAudio = true
|
||||
if let _ = updatedStatusSignal {
|
||||
updatedStatusSignal = .single(.Local)
|
||||
}
|
||||
|
||||
audioDuration = Int32(duration)
|
||||
if voice {
|
||||
isVoice = true
|
||||
candidateDescriptionString = NSAttributedString(string: String(format: "%d:%02d", duration / 60, duration % 60), font: durationFont, textColor:incoming ? incomingDurationColor : outgoingDurationColor)
|
||||
if let waveform = waveform {
|
||||
waveform.withDataNoCopy { data in
|
||||
audioWaveform = AudioWaveform(bitstream: data, bitsPerSample: 5)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
candidateTitleString = NSAttributedString(string: title ?? "Unknown Track", font: titleFont, textColor: incoming ? incomingTitleColor : outgoingTitleColor)
|
||||
let descriptionText: String
|
||||
if let performer = performer {
|
||||
descriptionText = performer
|
||||
} else if let size = file.size {
|
||||
descriptionText = dataSizeString(size)
|
||||
} else {
|
||||
descriptionText = ""
|
||||
}
|
||||
candidateDescriptionString = NSAttributedString(string: descriptionText, font: descriptionFont, textColor:incoming ? incomingDescriptionColor : outgoingDescriptionColor)
|
||||
}
|
||||
candidateDescriptionString = NSAttributedString(string: descriptionText, font: descriptionFont, textColor:incoming ? incomingDescriptionColor : outgoingDescriptionColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var titleString: NSAttributedString
|
||||
let descriptionString: NSAttributedString
|
||||
var titleString: NSAttributedString?
|
||||
var descriptionString: NSAttributedString?
|
||||
|
||||
if let candidateTitleString = candidateTitleString {
|
||||
titleString = candidateTitleString
|
||||
} else {
|
||||
} else if !isVoice {
|
||||
titleString = NSAttributedString(string: file.fileName ?? "File", font: titleFont, textColor: incoming ? incomingTitleColor : outgoingTitleColor)
|
||||
}
|
||||
|
||||
if let candidateDescriptionString = candidateDescriptionString {
|
||||
descriptionString = candidateDescriptionString
|
||||
} else {
|
||||
} else if !isVoice {
|
||||
let descriptionText: String
|
||||
if let size = file.size {
|
||||
descriptionText = dataSizeString(size)
|
||||
@@ -183,15 +248,50 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
let (titleLayout, titleApply) = titleAsyncLayout(titleString, nil, 1, .middle, textConstrainedSize, nil)
|
||||
let (descriptionLayout, descriptionApply) = descriptionAsyncLayout(descriptionString, nil, 1, .middle, textConstrainedSize, nil)
|
||||
|
||||
return (max(titleLayout.size.width, descriptionLayout.size.width) + 44.0 + 8.0, { boundingWidth in
|
||||
let progressFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
var voiceWidth: CGFloat = 0.0
|
||||
let minVoiceWidth: CGFloat = 120.0
|
||||
let maxVoiceWidth = constrainedSize.width
|
||||
let maxVoiceLength: CGFloat = 30.0
|
||||
|
||||
let minLayoutWidth: CGFloat
|
||||
if isVoice {
|
||||
//y = a exp bx
|
||||
//b = log (y1/y2) / (x1-x2)
|
||||
//a = y1 / exp bx1
|
||||
|
||||
let b = log(maxVoiceWidth / minVoiceWidth) / (maxVoiceLength - 0.0)
|
||||
let a = minVoiceWidth / exp(CGFloat(0.0))
|
||||
|
||||
let y = a * exp(b * CGFloat(audioDuration))
|
||||
|
||||
minLayoutWidth = floor(y)
|
||||
} else {
|
||||
minLayoutWidth = max(titleLayout.size.width, descriptionLayout.size.width) + 44.0 + 8.0
|
||||
}
|
||||
|
||||
return (minLayoutWidth, { boundingWidth in
|
||||
let progressDiameter: CGFloat = isVoice ? 37.0 : 44.0
|
||||
let progressFrame = CGRect(origin: CGPoint(x: 0.0, y: isVoice ? -5.0 : 0.0), size: CGSize(width: progressDiameter, height: progressDiameter))
|
||||
|
||||
let titleAndDescriptionHeight = titleLayout.size.height - 1.0 + descriptionLayout.size.height
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: progressFrame.maxX + 8.0, y: floor((44.0 - titleAndDescriptionHeight) / 2.0)), size: titleLayout.size)
|
||||
let descriptionFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY - 1.0), size: descriptionLayout.size)
|
||||
|
||||
return (titleFrame.union(descriptionFrame).union(progressFrame).size, { [weak self] in
|
||||
let descriptionFrame: CGRect
|
||||
if isVoice {
|
||||
descriptionFrame = CGRect(origin: CGPoint(x: 43.0, y: 19.0), size: descriptionLayout.size)
|
||||
} else {
|
||||
descriptionFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY - 1.0), size: descriptionLayout.size)
|
||||
}
|
||||
|
||||
let fittedLayoutSize: CGSize
|
||||
if isVoice {
|
||||
fittedLayoutSize = CGSize(width: minLayoutWidth, height: 27.0)
|
||||
} else {
|
||||
fittedLayoutSize = titleFrame.union(descriptionFrame).union(progressFrame).size
|
||||
}
|
||||
|
||||
return (fittedLayoutSize, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.messageIdAndFlags = (message.id, message.flags)
|
||||
strongSelf.file = file
|
||||
@@ -202,6 +302,27 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
strongSelf.descriptionNode.frame = descriptionFrame
|
||||
|
||||
if let statusApply = statusApply, let statusSize = statusSize {
|
||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
|
||||
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: fittedLayoutSize.width - statusSize.width, y: fittedLayoutSize.height - statusSize.height + 10.0), size: statusSize)
|
||||
statusApply(false)
|
||||
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if isVoice {
|
||||
if strongSelf.waveformNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.waveformNode)
|
||||
}
|
||||
strongSelf.waveformNode.frame = CGRect(origin: CGPoint(x: 43.0, y: -1.0), size: CGSize(width: fittedLayoutSize.width - 41.0, height: 12.0))
|
||||
strongSelf.waveformNode.setup(color: UIColor(incoming ? 0x007ee5 : 0x3fc33b), waveform: audioWaveform)
|
||||
} else if strongSelf.waveformNode.supernode != nil {
|
||||
strongSelf.waveformNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
/*if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.imageNode.setSignal(account, signal: updateImageSignal)
|
||||
}*/
|
||||
@@ -223,9 +344,17 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
case let .Fetching(progress):
|
||||
strongSelf.progressNode?.state = .Fetching(progress: progress)
|
||||
case .Local:
|
||||
strongSelf.progressNode?.state = .Play
|
||||
if isAudio {
|
||||
strongSelf.progressNode?.state = .Play
|
||||
} else {
|
||||
strongSelf.progressNode?.state = .Icon
|
||||
}
|
||||
case .Remote:
|
||||
strongSelf.progressNode?.state = .Remote
|
||||
if isAudio {
|
||||
strongSelf.progressNode?.state = .Play
|
||||
} else {
|
||||
strongSelf.progressNode?.state = .Remote
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,12 +373,12 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
}
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageInteractiveFileNode))) {
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageInteractiveFileNode))) {
|
||||
let currentAsyncLayout = node?.asyncLayout()
|
||||
|
||||
return { account, message, file, incoming, constrainedSize in
|
||||
return { account, message, file, incoming, dateAndStatusType, constrainedSize in
|
||||
var fileNode: ChatMessageInteractiveFileNode
|
||||
var fileLayout: (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void)))
|
||||
var fileLayout: (_ account: Account, _ message: Message, _ file: TelegramMediaFile, _ incoming: Bool, _ dateAnsStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void)))
|
||||
|
||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||
fileNode = node
|
||||
@@ -259,7 +388,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
||||
fileLayout = fileNode.asyncLayout()
|
||||
}
|
||||
|
||||
let (initialWidth, continueLayout) = fileLayout(account, message, file, incoming, constrainedSize)
|
||||
let (initialWidth, continueLayout) = fileLayout(account, message, file, incoming, dateAndStatusType, constrainedSize)
|
||||
|
||||
return (initialWidth, { constrainedSize in
|
||||
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
||||
|
||||
Reference in New Issue
Block a user