|
|
|
@ -32,6 +32,7 @@ enum InternalBubbleTapAction {
|
|
|
|
|
private struct BubbleItemAttributes {
|
|
|
|
|
var isAttachment: Bool
|
|
|
|
|
var neighborType: ChatMessageBubbleRelativePosition.NeighbourType
|
|
|
|
|
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] {
|
|
|
|
@ -41,52 +42,61 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
|
|
|
|
|
var isUnsupportedMedia = false
|
|
|
|
|
var isAction = false
|
|
|
|
|
|
|
|
|
|
var previousItemIsMusic = false
|
|
|
|
|
|
|
|
|
|
outer: for (message, itemAttributes) in item.content {
|
|
|
|
|
for attribute in message.attributes {
|
|
|
|
|
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
|
|
|
|
|
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
break outer
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inner: for media in message.media {
|
|
|
|
|
var isMusic = false
|
|
|
|
|
if let _ = media as? TelegramMediaImage {
|
|
|
|
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media)))
|
|
|
|
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
|
|
|
|
} else if let file = media as? TelegramMediaFile {
|
|
|
|
|
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
|
|
|
|
|
if isVideo {
|
|
|
|
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media)))
|
|
|
|
|
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
|
|
|
|
} else {
|
|
|
|
|
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default
|
|
|
|
|
if previousItemIsMusic {
|
|
|
|
|
neighborSpacing = .condensed
|
|
|
|
|
}
|
|
|
|
|
isMusic = file.isMusic
|
|
|
|
|
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: neighborSpacing)))
|
|
|
|
|
}
|
|
|
|
|
} else if let action = media as? TelegramMediaAction {
|
|
|
|
|
isAction = true
|
|
|
|
|
if case .phoneCall = action.action {
|
|
|
|
|
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
} else {
|
|
|
|
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
} else if let _ = media as? TelegramMediaMap {
|
|
|
|
|
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
} else if let _ = media as? TelegramMediaGame {
|
|
|
|
|
skipText = true
|
|
|
|
|
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
break inner
|
|
|
|
|
} else if let _ = media as? TelegramMediaInvoice {
|
|
|
|
|
skipText = true
|
|
|
|
|
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
break inner
|
|
|
|
|
} else if let _ = media as? TelegramMediaContact {
|
|
|
|
|
result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
} else if let _ = media as? TelegramMediaExpiredContent {
|
|
|
|
|
result.removeAll()
|
|
|
|
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
return result
|
|
|
|
|
} else if let _ = media as? TelegramMediaPoll {
|
|
|
|
|
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
} else if let _ = media as? TelegramMediaUnsupported {
|
|
|
|
|
isUnsupportedMedia = true
|
|
|
|
|
}
|
|
|
|
|
previousItemIsMusic = isMusic
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var messageText = message.text
|
|
|
|
@ -100,7 +110,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
|
|
|
|
|
messageWithCaptionToAdd = (message, itemAttributes)
|
|
|
|
|
skipText = true
|
|
|
|
|
} else {
|
|
|
|
|
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if case .group = item.content {
|
|
|
|
@ -112,29 +122,29 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
|
|
|
|
|
inner: for media in message.media {
|
|
|
|
|
if let webpage = media as? TelegramMediaWebpage {
|
|
|
|
|
if case .Loaded = webpage.content {
|
|
|
|
|
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
break inner
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isUnsupportedMedia {
|
|
|
|
|
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd {
|
|
|
|
|
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let additionalContent = item.additionalContent {
|
|
|
|
|
switch additionalContent {
|
|
|
|
|
case let .eventLogPreviousMessage(previousMessage):
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
case let .eventLogPreviousDescription(previousMessage):
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
case let .eventLogPreviousLink(previousMessage):
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform)))
|
|
|
|
|
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -167,10 +177,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if canComment {
|
|
|
|
|
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform)))
|
|
|
|
|
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
} else if firstMessage.id.peerId.isReplies {
|
|
|
|
|
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform)))
|
|
|
|
|
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1118,8 +1128,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
let topPosition: ChatMessageBubbleRelativePosition
|
|
|
|
|
let bottomPosition: ChatMessageBubbleRelativePosition
|
|
|
|
|
|
|
|
|
|
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform)
|
|
|
|
|
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform)
|
|
|
|
|
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
|
|
|
|
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
|
|
|
|
if index != 0 {
|
|
|
|
|
topBubbleAttributes = contentPropertiesAndPrepareLayouts[index - 1].3
|
|
|
|
|
}
|
|
|
|
@ -1127,8 +1137,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
bottomBubbleAttributes = contentPropertiesAndPrepareLayouts[index + 1].3
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType)
|
|
|
|
|
bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType)
|
|
|
|
|
topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType, topBubbleAttributes.neighborSpacing)
|
|
|
|
|
bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType, bottomBubbleAttributes.neighborSpacing)
|
|
|
|
|
|
|
|
|
|
let prepareContentPosition: ChatMessageBubblePreparePosition
|
|
|
|
|
if let mosaicRange = mosaicRange, mosaicRange.contains(index) {
|
|
|
|
@ -1262,7 +1272,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
|
|
|
|
|
let firstNodeTopPosition: ChatMessageBubbleRelativePosition
|
|
|
|
|
if displayHeader {
|
|
|
|
|
firstNodeTopPosition = .Neighbour(false, .freeform)
|
|
|
|
|
firstNodeTopPosition = .Neighbour(false, .freeform, .default)
|
|
|
|
|
} else {
|
|
|
|
|
firstNodeTopPosition = .None(topNodeMergeStatus)
|
|
|
|
|
}
|
|
|
|
@ -1556,7 +1566,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
if mosaicRange.upperBound - 1 == contentNodeCount - 1 {
|
|
|
|
|
lastMosaicBottomPosition = lastNodeTopPosition
|
|
|
|
|
} else {
|
|
|
|
|
lastMosaicBottomPosition = .Neighbour(false, .freeform)
|
|
|
|
|
lastMosaicBottomPosition = .Neighbour(false, .freeform, .default)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition {
|
|
|
|
@ -1619,8 +1629,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
let topPosition: ChatMessageBubbleRelativePosition
|
|
|
|
|
let bottomPosition: ChatMessageBubbleRelativePosition
|
|
|
|
|
|
|
|
|
|
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform)
|
|
|
|
|
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform)
|
|
|
|
|
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
|
|
|
|
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
|
|
|
|
if i != 0 {
|
|
|
|
|
topBubbleAttributes = contentPropertiesAndLayouts[i - 1].3
|
|
|
|
|
}
|
|
|
|
@ -1631,19 +1641,19 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|
|
|
|
if i == 0 {
|
|
|
|
|
topPosition = firstNodeTopPosition
|
|
|
|
|
} else {
|
|
|
|
|
topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType)
|
|
|
|
|
topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType, topBubbleAttributes.neighborSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i == contentNodeCount - 1 {
|
|
|
|
|
bottomPosition = lastNodeTopPosition
|
|
|
|
|
} else {
|
|
|
|
|
bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType)
|
|
|
|
|
bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType, bottomBubbleAttributes.neighborSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contentPosition = .linear(top: topPosition, bottom: bottomPosition)
|
|
|
|
|
case .mosaic:
|
|
|
|
|
assertionFailure()
|
|
|
|
|
contentPosition = .linear(top: .Neighbour(false, .freeform), bottom: .Neighbour(false, .freeform))
|
|
|
|
|
contentPosition = .linear(top: .Neighbour(false, .freeform, .default), bottom: .Neighbour(false, .freeform, .default))
|
|
|
|
|
}
|
|
|
|
|
let (contentNodeWidth, contentNodeFinalize) = contentNodeLayout(CGSize(width: maximumNodeWidth, height: CGFloat.greatestFiniteMagnitude), contentPosition)
|
|
|
|
|
#if DEBUG
|
|
|
|
|