mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
694 lines
43 KiB
Swift
694 lines
43 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
|
|
private let titleFont = Font.regular(13.0)
|
|
private let titleBoldFont = Font.bold(13.0)
|
|
|
|
private func peerMentionAttributes(primaryTextColor: UIColor, peerId: PeerId) -> MarkdownAttributeSet {
|
|
return MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.PeerMention: TelegramPeerMention(peerId: peerId, mention: "")])
|
|
}
|
|
|
|
private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, PeerId?)]) -> [Int: MarkdownAttributeSet] {
|
|
var result: [Int: MarkdownAttributeSet] = [:]
|
|
for (index, peerId) in peerIds {
|
|
if let peerId = peerId {
|
|
result[index] = peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: peerId)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
|
|
return universalServiceMessageString(theme: theme, strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId)
|
|
}
|
|
|
|
func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> String? {
|
|
return universalServiceMessageString(theme: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId)?.string
|
|
}
|
|
|
|
private func universalServiceMessageString(theme: ChatPresentationThemeData?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
|
|
var attributedString: NSAttributedString?
|
|
|
|
let primaryTextColor: UIColor
|
|
if let theme = theme {
|
|
primaryTextColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper).primaryText
|
|
} else {
|
|
primaryTextColor = .black
|
|
}
|
|
|
|
let bodyAttributes = MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [:])
|
|
|
|
for media in message.media {
|
|
if let action = media as? TelegramMediaAction {
|
|
let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
|
|
|
|
var isChannel = false
|
|
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
|
isChannel = true
|
|
}
|
|
|
|
switch action.action {
|
|
case let .groupCreated(title):
|
|
if isChannel {
|
|
attributedString = NSAttributedString(string: strings.Notification_CreatedChannel, font: titleFont, textColor: primaryTextColor)
|
|
} else {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_CreatedChatWithTitle(authorName, title), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
}
|
|
case let .addedMembers(peerIds):
|
|
if let peerId = peerIds.first, peerId == message.author?.id {
|
|
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedChannel(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
|
|
} else {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedChat(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
|
|
}
|
|
} else {
|
|
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
|
if peerIds.count == 1 {
|
|
attributePeerIds.append((1, peerIds.first))
|
|
}
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_Invited(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
|
}
|
|
case let .removedMembers(peerIds):
|
|
if peerIds.first == message.author?.id {
|
|
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_LeftChannel(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
} else {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_LeftChat(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
}
|
|
} else {
|
|
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
|
if peerIds.count == 1 {
|
|
attributePeerIds.append((1, peerIds.first))
|
|
}
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
|
}
|
|
case let .photoUpdated(image):
|
|
if authorName.isEmpty || isChannel {
|
|
if isChannel {
|
|
if image != nil {
|
|
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
|
|
} else {
|
|
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
|
|
}
|
|
} else {
|
|
if image != nil {
|
|
attributedString = NSAttributedString(string: strings.Group_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
|
|
} else {
|
|
attributedString = NSAttributedString(string: strings.Group_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
|
|
}
|
|
}
|
|
} else {
|
|
if image != nil {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
} else {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_RemovedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
}
|
|
}
|
|
case let .titleUpdated(title):
|
|
if authorName.isEmpty || isChannel {
|
|
attributedString = NSAttributedString(string: strings.Channel_MessageTitleUpdated(title).0, font: titleFont, textColor: primaryTextColor)
|
|
} else {
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupName(authorName, title), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
}
|
|
case .pinnedMessageUpdated:
|
|
enum PinnnedMediaType {
|
|
case text(String)
|
|
case game
|
|
case photo
|
|
case video
|
|
case round
|
|
case audio
|
|
case file
|
|
case gif
|
|
case sticker
|
|
case location
|
|
case contact
|
|
case poll
|
|
case deleted
|
|
}
|
|
|
|
var pinnedMessage: Message?
|
|
for attribute in message.attributes {
|
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
|
pinnedMessage = message
|
|
}
|
|
}
|
|
|
|
var type: PinnnedMediaType
|
|
if let pinnedMessage = pinnedMessage {
|
|
type = .text(pinnedMessage.text)
|
|
inner: for media in pinnedMessage.media {
|
|
if media is TelegramMediaGame {
|
|
type = .game
|
|
break inner
|
|
}
|
|
if let _ = media as? TelegramMediaImage {
|
|
type = .photo
|
|
} else if let file = media as? TelegramMediaFile {
|
|
type = .file
|
|
if file.isAnimated {
|
|
type = .gif
|
|
} else {
|
|
for attribute in file.attributes {
|
|
switch attribute {
|
|
case let .Video(_, _, flags):
|
|
if flags.contains(.instantRoundVideo) {
|
|
type = .round
|
|
} else {
|
|
type = .video
|
|
}
|
|
break inner
|
|
case let .Audio(isVoice, _, _, _, _):
|
|
if isVoice {
|
|
type = .audio
|
|
} else {
|
|
type = .file
|
|
}
|
|
break inner
|
|
case .Sticker:
|
|
type = .sticker
|
|
break inner
|
|
case .Animated:
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
} else if let _ = media as? TelegramMediaMap {
|
|
type = .location
|
|
} else if let _ = media as? TelegramMediaContact {
|
|
type = .contact
|
|
} else if let _ = media as? TelegramMediaPoll {
|
|
type = .poll
|
|
}
|
|
}
|
|
} else {
|
|
type = .deleted
|
|
}
|
|
|
|
switch type {
|
|
case let .text(text):
|
|
var clippedText = text.replacingOccurrences(of: "\n", with: " ")
|
|
if clippedText.count > 14 {
|
|
clippedText = "\(clippedText[...clippedText.index(clippedText.startIndex, offsetBy: 14)])..."
|
|
}
|
|
let textWithRanges: (String, [(Int, NSRange)])
|
|
if clippedText.isEmpty {
|
|
textWithRanges = strings.PUSH_PINNED_NOTEXT(authorName)
|
|
} else {
|
|
textWithRanges = strings.Notification_PinnedTextMessage(authorName, clippedText)
|
|
}
|
|
attributedString = addAttributesToStringWithRanges(textWithRanges, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .game:
|
|
attributedString = addAttributesToStringWithRanges(strings.PUSH_PINNED_GAME(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .photo:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedPhotoMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .video:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedVideoMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .round:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedRoundMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .audio:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedAudioMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .file:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedDocumentMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .gif:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedAnimationMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .sticker:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedStickerMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .location:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedLocationMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .contact:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedContactMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .poll:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedPollMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .deleted:
|
|
attributedString = addAttributesToStringWithRanges(strings.PUSH_PINNED_NOTEXT(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
}
|
|
case .joinedByLink:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
|
attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor)
|
|
case let .messageAutoremoveTimeoutUpdated(timeout):
|
|
if timeout > 0 {
|
|
let timeValue = timeIntervalString(strings: strings, value: timeout)
|
|
|
|
let string: String
|
|
if message.author?.id == accountPeerId {
|
|
string = strings.Notification_MessageLifetimeChangedOutgoing(timeValue).0
|
|
} else {
|
|
let authorString: String
|
|
if let author = messageMainPeer(message) {
|
|
authorString = author.compactDisplayTitle
|
|
} else {
|
|
authorString = ""
|
|
}
|
|
string = strings.Notification_MessageLifetimeChanged(authorString, timeValue).0
|
|
}
|
|
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)
|
|
} else {
|
|
let string: String
|
|
if message.author?.id == accountPeerId {
|
|
string = strings.Notification_MessageLifetimeRemovedOutgoing
|
|
} else {
|
|
let authorString: String
|
|
if let author = messageMainPeer(message) {
|
|
authorString = author.compactDisplayTitle
|
|
} else {
|
|
authorString = ""
|
|
}
|
|
string = strings.Notification_MessageLifetimeRemoved(authorString).0
|
|
}
|
|
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)
|
|
}
|
|
case .historyCleared:
|
|
break
|
|
case .historyScreenshot:
|
|
let text: String
|
|
if message.effectivelyIncoming(accountPeerId) {
|
|
text = strings.Notification_SecretChatMessageScreenshot(message.author?.compactDisplayTitle ?? "").0
|
|
} else {
|
|
text = strings.Notification_SecretChatMessageScreenshotSelf
|
|
}
|
|
attributedString = NSAttributedString(string: text, font: titleFont, textColor: primaryTextColor)
|
|
case let .gameScore(gameId: _, score):
|
|
var gameTitle: String?
|
|
inner: for attribute in message.attributes {
|
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
|
for media in message.media {
|
|
if let game = media as? TelegramMediaGame {
|
|
gameTitle = game.title
|
|
break inner
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var baseString: String
|
|
if message.author?.id == accountPeerId {
|
|
if let _ = gameTitle {
|
|
baseString = strings.ServiceMessage_GameScoreSelfExtended(score)
|
|
} else {
|
|
baseString = strings.ServiceMessage_GameScoreSelfSimple(score)
|
|
}
|
|
} else {
|
|
if let _ = gameTitle {
|
|
baseString = strings.ServiceMessage_GameScoreExtended(score)
|
|
} else {
|
|
baseString = strings.ServiceMessage_GameScoreSimple(score)
|
|
}
|
|
}
|
|
let baseStringValue = baseString as NSString
|
|
var ranges: [(Int, NSRange)] = []
|
|
if baseStringValue.range(of: "{name}").location != NSNotFound {
|
|
ranges.append((0, baseStringValue.range(of: "{name}")))
|
|
}
|
|
if baseStringValue.range(of: "{game}").location != NSNotFound {
|
|
ranges.append((1, baseStringValue.range(of: "{game}")))
|
|
}
|
|
ranges.sort(by: { $0.1.location < $1.1.location })
|
|
|
|
var argumentAttributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
|
argumentAttributes[1] = MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [:])
|
|
attributedString = addAttributesToStringWithRanges(formatWithArgumentRanges(baseString, ranges, [authorName, gameTitle ?? ""]), body: bodyAttributes, argumentAttributes: argumentAttributes)
|
|
case let .paymentSent(currency, totalAmount):
|
|
var invoiceMessage: Message?
|
|
for attribute in message.attributes {
|
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
|
invoiceMessage = message
|
|
}
|
|
}
|
|
|
|
var invoiceTitle: String?
|
|
if let invoiceMessage = invoiceMessage {
|
|
for media in invoiceMessage.media {
|
|
if let invoice = media as? TelegramMediaInvoice {
|
|
invoiceTitle = invoice.title
|
|
}
|
|
}
|
|
}
|
|
|
|
if let invoiceTitle = invoiceTitle {
|
|
let botString: String
|
|
if let peer = messageMainPeer(message) {
|
|
botString = peer.compactDisplayTitle
|
|
} else {
|
|
botString = ""
|
|
}
|
|
let mutableString = NSMutableAttributedString()
|
|
mutableString.append(NSAttributedString(string: strings.Notification_PaymentSent, font: titleFont, textColor: primaryTextColor))
|
|
|
|
var range = NSRange(location: NSNotFound, length: 0)
|
|
|
|
range = (mutableString.string as NSString).range(of: "{amount}")
|
|
if range.location != NSNotFound {
|
|
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor))
|
|
}
|
|
range = (mutableString.string as NSString).range(of: "{name}")
|
|
if range.location != NSNotFound {
|
|
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: botString, font: titleBoldFont, textColor: primaryTextColor))
|
|
}
|
|
range = (mutableString.string as NSString).range(of: "{title}")
|
|
if range.location != NSNotFound {
|
|
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: invoiceTitle, font: titleFont, textColor: primaryTextColor))
|
|
}
|
|
attributedString = mutableString
|
|
} else {
|
|
attributedString = NSAttributedString(string: strings.Message_PaymentSent(formatCurrencyAmount(totalAmount, currency: currency)).0, font: titleFont, textColor: primaryTextColor)
|
|
}
|
|
case let .phoneCall(_, discardReason, _):
|
|
var titleString: String
|
|
let incoming: Bool
|
|
if message.flags.contains(.Incoming) {
|
|
titleString = strings.Notification_CallIncoming
|
|
incoming = true
|
|
} else {
|
|
titleString = strings.Notification_CallOutgoing
|
|
incoming = false
|
|
}
|
|
if let discardReason = discardReason {
|
|
switch discardReason {
|
|
case .busy, .disconnect:
|
|
titleString = strings.Notification_CallCanceled
|
|
case .missed:
|
|
titleString = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
|
|
case .hangup:
|
|
break
|
|
}
|
|
}
|
|
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
|
case let .customText(text, entities):
|
|
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, fixedFont: titleFont, underlineLinks: false)
|
|
case let .botDomainAccessGranted(domain):
|
|
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
|
|
case let .botSentSecureValues(types):
|
|
var typesString = ""
|
|
var hasIdentity = false
|
|
var hasAddress = false
|
|
for type in types {
|
|
if !typesString.isEmpty {
|
|
typesString.append(", ")
|
|
}
|
|
switch type {
|
|
case .personalDetails:
|
|
typesString.append(strings.Notification_PassportValuePersonalDetails)
|
|
case .passport, .internalPassport, .driversLicense, .idCard:
|
|
if !hasIdentity {
|
|
typesString.append(strings.Notification_PassportValueProofOfIdentity)
|
|
hasIdentity = true
|
|
}
|
|
case .address:
|
|
typesString.append(strings.Notification_PassportValueAddress)
|
|
case .bankStatement, .utilityBill, .rentalAgreement, .passportRegistration, .temporaryRegistration:
|
|
if !hasAddress {
|
|
typesString.append(strings.Notification_PassportValueProofOfAddress)
|
|
hasAddress = true
|
|
}
|
|
case .phone:
|
|
typesString.append(strings.Notification_PassportValuePhone)
|
|
case .email:
|
|
typesString.append(strings.Notification_PassportValueEmail)
|
|
}
|
|
}
|
|
attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.peers[message.id.peerId]?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor)
|
|
case .peerJoined:
|
|
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
|
case .unknown:
|
|
attributedString = nil
|
|
}
|
|
|
|
break
|
|
} else if let expiredMedia = media as? TelegramMediaExpiredContent {
|
|
switch expiredMedia.data {
|
|
case .image:
|
|
attributedString = NSAttributedString(string: strings.Message_ImageExpired, font: titleFont, textColor: primaryTextColor)
|
|
case .file:
|
|
attributedString = NSAttributedString(string: strings.Message_VideoExpired, font: titleFont, textColor: primaryTextColor)
|
|
}
|
|
}
|
|
}
|
|
|
|
return attributedString
|
|
}
|
|
|
|
class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|
let labelNode: TextNode
|
|
let filledBackgroundNode: LinkHighlightingNode
|
|
var linkHighlightingNode: LinkHighlightingNode?
|
|
fileprivate var imageNode: TransformImageNode?
|
|
private let fetchDisposable = MetaDisposable()
|
|
|
|
required init() {
|
|
self.labelNode = TextNode()
|
|
self.labelNode.isUserInteractionEnabled = false
|
|
self.labelNode.displaysAsynchronously = true
|
|
|
|
self.filledBackgroundNode = LinkHighlightingNode(color: .clear)
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.filledBackgroundNode)
|
|
self.addSubnode(self.labelNode)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.fetchDisposable.dispose()
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
}
|
|
|
|
override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, () -> UIView?)? {
|
|
if let imageNode = self.imageNode, self.item?.message.id == messageId {
|
|
return (imageNode, { [weak imageNode] in
|
|
return imageNode?.view.snapshotContentTree(unhide: true)
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
override func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
|
var mediaHidden = false
|
|
var currentMedia: Media?
|
|
if let item = item {
|
|
mediaLoop: for media in item.message.media {
|
|
if let media = media as? TelegramMediaAction {
|
|
switch media.action {
|
|
case let .photoUpdated(image):
|
|
currentMedia = image
|
|
break mediaLoop
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let currentMedia = currentMedia, let media = media {
|
|
for item in media {
|
|
if item.isSemanticallyEqual(to: currentMedia) {
|
|
mediaHidden = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
self.imageNode?.isHidden = mediaHidden
|
|
return mediaHidden
|
|
}
|
|
|
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
|
|
|
let backgroundLayout = self.filledBackgroundNode.asyncLayout()
|
|
|
|
return { item, layoutConstants, _, _, _ in
|
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
|
|
|
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
|
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: item.message, accountPeerId: item.context.account.peerId)
|
|
|
|
var image: TelegramMediaImage?
|
|
for media in item.message.media {
|
|
if let action = media as? TelegramMediaAction {
|
|
switch action.action {
|
|
case let .photoUpdated(img):
|
|
image = img
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let imageSize = CGSize(width: 70.0, height: 70.0)
|
|
|
|
let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
|
|
|
var labelRects = labelLayout.linesRects()
|
|
if labelRects.count > 1 {
|
|
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
|
|
for i in 0 ..< sortedIndices.count {
|
|
let index = sortedIndices[i]
|
|
for j in -1 ... 1 {
|
|
if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
|
|
if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 {
|
|
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
|
|
labelRects[index].size.width = labelRects[index + j].size.width
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for i in 0 ..< labelRects.count {
|
|
labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0))
|
|
labelRects[i].size.height = 20.0
|
|
labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0)
|
|
}
|
|
|
|
|
|
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
|
|
let backgroundApply = backgroundLayout(serviceColor.fill, labelRects, 10.0, 10.0, 0.0)
|
|
|
|
var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0)
|
|
let layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0)
|
|
|
|
if let _ = image {
|
|
backgroundSize.height += imageSize.height + 10
|
|
}
|
|
|
|
return (backgroundSize.width, { boundingWidth in
|
|
return (backgroundSize, { [weak self] animation, _ in
|
|
if let strongSelf = self {
|
|
strongSelf.item = item
|
|
|
|
if let image = image {
|
|
let imageNode: TransformImageNode
|
|
if let current = strongSelf.imageNode {
|
|
imageNode = current
|
|
} else {
|
|
imageNode = TransformImageNode()
|
|
strongSelf.imageNode = imageNode
|
|
strongSelf.insertSubnode(imageNode, at: 0)
|
|
let arguments = TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())
|
|
let apply = imageNode.asyncLayout()(arguments)
|
|
apply()
|
|
|
|
}
|
|
strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(context: item.context, photoReference: .message(message: MessageReference(item.message), media: image), storeToDownloadsPeerType: nil).start())
|
|
let updateImageSignal = chatMessagePhoto(postbox: item.context.account.postbox, photoReference: .message(message: MessageReference(item.message), media: image))
|
|
|
|
imageNode.setSignal(updateImageSignal)
|
|
|
|
imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize)
|
|
} else if let imageNode = strongSelf.imageNode {
|
|
imageNode.removeFromSupernode()
|
|
strongSelf.imageNode = nil
|
|
}
|
|
|
|
let _ = apply()
|
|
let _ = backgroundApply()
|
|
|
|
let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: image != nil ? 2 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size)
|
|
strongSelf.labelNode.frame = labelFrame
|
|
strongSelf.filledBackgroundNode.frame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
override func updateTouchesAtPoint(_ point: CGPoint?) {
|
|
if let item = self.item {
|
|
var rects: [(CGRect, CGRect)]?
|
|
let textNodeFrame = self.labelNode.frame
|
|
if let point = point {
|
|
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) {
|
|
let possibleNames: [String] = [
|
|
TelegramTextAttributes.URL,
|
|
TelegramTextAttributes.PeerMention,
|
|
TelegramTextAttributes.PeerTextMention,
|
|
TelegramTextAttributes.BotCommand,
|
|
TelegramTextAttributes.Hashtag
|
|
]
|
|
for name in possibleNames {
|
|
if let _ = attributes[NSAttributedStringKey(rawValue: name)] {
|
|
rects = self.labelNode.lineAndAttributeRects(name: name, at: index)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let rects = rects {
|
|
var mappedRects: [CGRect] = []
|
|
for i in 0 ..< rects.count {
|
|
let lineRect = rects[i].0
|
|
var itemRect = rects[i].1
|
|
itemRect.origin.x = floor((textNodeFrame.size.width - lineRect.width) / 2.0) + itemRect.origin.x
|
|
mappedRects.append(itemRect)
|
|
}
|
|
|
|
let linkHighlightingNode: LinkHighlightingNode
|
|
if let current = self.linkHighlightingNode {
|
|
linkHighlightingNode = current
|
|
} else {
|
|
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
|
|
linkHighlightingNode = LinkHighlightingNode(color: serviceColor.linkHighlight)
|
|
linkHighlightingNode.inset = 2.5
|
|
self.linkHighlightingNode = linkHighlightingNode
|
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.labelNode)
|
|
}
|
|
linkHighlightingNode.frame = self.labelNode.frame.offsetBy(dx: 0.0, dy: 1.5)
|
|
linkHighlightingNode.updateRects(mappedRects)
|
|
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
|
self.linkHighlightingNode = nil
|
|
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
|
linkHighlightingNode?.removeFromSupernode()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
|
let textNodeFrame = self.labelNode.frame
|
|
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap {
|
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
|
var concealed = true
|
|
if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
|
concealed = !doesUrlMatchText(url: url, text: attributeText)
|
|
}
|
|
return .url(url: url, concealed: concealed)
|
|
} else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
|
return .peerMention(peerMention.peerId, peerMention.mention)
|
|
} else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
|
return .textMention(peerName)
|
|
} else if let botCommand = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
|
return .botCommand(botCommand)
|
|
} else if let hashtag = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
|
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
|
}
|
|
}
|
|
if let imageNode = imageNode, imageNode.frame.contains(point) {
|
|
return .openMessage
|
|
}
|
|
|
|
if self.filledBackgroundNode.frame.contains(point.offsetBy(dx: 0.0, dy: -10.0)) {
|
|
return .openMessage
|
|
} else {
|
|
return .none
|
|
}
|
|
}
|
|
}
|