mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
489 lines
25 KiB
Swift
489 lines
25 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import AvatarNode
|
|
import AccountContext
|
|
import LocalizedPeerData
|
|
import StickerResources
|
|
import PhotoResources
|
|
import TelegramStringFormatting
|
|
import TextFormat
|
|
import InvisibleInkDustNode
|
|
import TextNodeWithEntities
|
|
import AnimationCache
|
|
import MultiAnimationRenderer
|
|
|
|
public final class ChatMessageNotificationItem: NotificationItem {
|
|
public let context: AccountContext
|
|
public let strings: PresentationStrings
|
|
public let dateTimeFormat: PresentationDateTimeFormat
|
|
public let nameDisplayOrder: PresentationPersonNameOrder
|
|
public let messages: [Message]
|
|
public let threadData: MessageHistoryThreadData?
|
|
public let tapAction: () -> Bool
|
|
public let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
|
|
|
|
public var groupingKey: AnyHashable? {
|
|
return messages.first?.id.peerId
|
|
}
|
|
|
|
public init(context: AccountContext, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messages: [Message], threadData: MessageHistoryThreadData?, tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) {
|
|
self.context = context
|
|
self.strings = strings
|
|
self.dateTimeFormat = dateTimeFormat
|
|
self.nameDisplayOrder = nameDisplayOrder
|
|
self.messages = messages
|
|
self.threadData = threadData
|
|
self.tapAction = tapAction
|
|
self.expandAction = expandAction
|
|
}
|
|
|
|
public func node(compact: Bool) -> NotificationItemNode {
|
|
let node = ChatMessageNotificationItemNode()
|
|
node.setupItem(self, compact: compact)
|
|
return node
|
|
}
|
|
|
|
public func tapped(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) {
|
|
if self.tapAction() {
|
|
self.expandAction(take)
|
|
}
|
|
}
|
|
|
|
public func canBeExpanded() -> Bool {
|
|
return true
|
|
}
|
|
|
|
public func expand(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) {
|
|
self.expandAction(take)
|
|
}
|
|
}
|
|
|
|
private let compactAvatarFont = avatarPlaceholderFont(size: 20.0)
|
|
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
|
|
|
private let telegramCodeRegex = try? NSRegularExpression(pattern: "(?<=: )\\b\\d{5,8}\\b(?=\\.)", options: [])
|
|
private let loginCodeRegex = try? NSRegularExpression(pattern: "\\b\\d{5,8}\\b", options: [])
|
|
|
|
final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|
private var item: ChatMessageNotificationItem?
|
|
|
|
private let avatarNode: AvatarNode
|
|
private let titleIconNode: ASImageNode
|
|
private let titleNode: TextNode
|
|
private let textNode: TextNodeWithEntities
|
|
private var dustNode: InvisibleInkDustNode?
|
|
private let imageNode: TransformImageNode
|
|
|
|
private var titleAttributedText: NSAttributedString?
|
|
private var textAttributedText: NSAttributedString?
|
|
|
|
private var compact: Bool?
|
|
private var validLayout: CGFloat?
|
|
|
|
override init() {
|
|
self.avatarNode = AvatarNode(font: avatarFont)
|
|
|
|
self.titleNode = TextNode()
|
|
self.titleNode.isUserInteractionEnabled = false
|
|
|
|
self.titleIconNode = ASImageNode()
|
|
self.titleIconNode.isLayerBacked = true
|
|
self.titleIconNode.displayWithoutProcessing = true
|
|
self.titleIconNode.displaysAsynchronously = false
|
|
|
|
self.textNode = TextNodeWithEntities()
|
|
self.textNode.textNode.isUserInteractionEnabled = false
|
|
|
|
self.imageNode = TransformImageNode()
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.avatarNode)
|
|
self.addSubnode(self.titleIconNode)
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.textNode.textNode)
|
|
self.addSubnode(self.imageNode)
|
|
}
|
|
|
|
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
|
self.item = item
|
|
|
|
self.compact = compact
|
|
if compact {
|
|
self.avatarNode.font = compactAvatarFont
|
|
}
|
|
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
var isReminder = false
|
|
var isScheduled = false
|
|
var title: String?
|
|
if let firstMessage = item.messages.first, let peer = messageMainPeer(EngineMessage(firstMessage)) {
|
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
} else if let author = firstMessage.author {
|
|
if firstMessage.id.peerId.isReplies, let _ = firstMessage.sourceReference, let effectiveAuthor = firstMessage.forwardInfo?.author {
|
|
title = EnginePeer(effectiveAuthor).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
} else if author.id != peer.id {
|
|
let authorString: String
|
|
if author.id == item.context.account.peerId {
|
|
authorString = presentationData.strings.DialogList_You
|
|
} else {
|
|
authorString = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
}
|
|
|
|
if let threadData = item.threadData {
|
|
title = "\(authorString) → \(threadData.info.title)"
|
|
} else {
|
|
title = authorString + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
}
|
|
} else {
|
|
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
for attribute in firstMessage.attributes {
|
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
|
if let sourcePeer = firstMessage.peers[attribute.messageId.peerId] {
|
|
title = EnginePeer(sourcePeer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if let titleValue = title, let threadData = item.threadData {
|
|
title = "\(threadData.info.title) (\(titleValue))"
|
|
}
|
|
}
|
|
} else {
|
|
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
}
|
|
|
|
if let _ = title, firstMessage.flags.contains(.WasScheduled) {
|
|
if let author = firstMessage.author, author.id == peer.id, author.id == item.context.account.peerId {
|
|
isReminder = true
|
|
} else {
|
|
isScheduled = true
|
|
}
|
|
}
|
|
var avatarPeer = peer
|
|
if firstMessage.id.peerId.isRepliesOrVerificationCodes, let author = firstMessage.forwardInfo?.author {
|
|
avatarPeer = EnginePeer(author)
|
|
}
|
|
self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: avatarPeer, overrideImage: peer.id == item.context.account.peerId ? .savedMessagesIcon : nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor)
|
|
}
|
|
|
|
var updatedMedia: Media?
|
|
var imageDimensions: CGSize?
|
|
var isRound = false
|
|
var messageText: String
|
|
var messageEntities: [MessageTextEntity]?
|
|
if item.messages.first?.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
|
messageText = item.strings.PUSH_ENCRYPTED_MESSAGE("").string
|
|
} else if item.messages.count == 1 {
|
|
let message = item.messages[0]
|
|
for media in message.media {
|
|
if let image = media as? TelegramMediaImage {
|
|
updatedMedia = image
|
|
if let representation = largestRepresentationForPhoto(image) {
|
|
imageDimensions = representation.dimensions.cgSize
|
|
}
|
|
break
|
|
} else if let file = media as? TelegramMediaFile {
|
|
updatedMedia = file
|
|
if let representation = largestImageRepresentation(file.previewRepresentations) {
|
|
imageDimensions = representation.dimensions.cgSize
|
|
}
|
|
isRound = file.isInstantVideo
|
|
break
|
|
}
|
|
}
|
|
if message.containsSecretMedia {
|
|
imageDimensions = nil
|
|
}
|
|
let (textString, _, isText) = descriptionStringForMessage(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, dateTimeFormat: item.dateTimeFormat, accountPeerId: item.context.account.peerId)
|
|
if isText {
|
|
messageText = message.text
|
|
messageEntities = message.textEntitiesAttribute?.entities.filter { entity in
|
|
if case .Spoiler = entity.type {
|
|
return true
|
|
} else if case .CustomEmoji = entity.type {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
if messageEntities?.count == 0 {
|
|
messageEntities = nil
|
|
messageText = textString.string
|
|
}
|
|
} else {
|
|
messageText = textString.string
|
|
}
|
|
} else if item.messages.count > 1, let peer = item.messages[0].peers[item.messages[0].id.peerId] {
|
|
var displayAuthor = true
|
|
if let channel = peer as? TelegramChannel {
|
|
switch channel.info {
|
|
case .group:
|
|
displayAuthor = true
|
|
case .broadcast:
|
|
displayAuthor = false
|
|
}
|
|
} else if let _ = peer as? TelegramUser {
|
|
displayAuthor = false
|
|
}
|
|
|
|
if item.messages[0].forwardInfo != nil && item.messages[0].sourceReference == nil {
|
|
if let author = item.messages[0].author, displayAuthor {
|
|
if !isReminder {
|
|
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
}
|
|
messageText = presentationData.strings.PUSH_CHAT_MESSAGE_FWDS_TEXT(Int32(item.messages.count)).replacingOccurrences(of: "{author}", with: EnginePeer(author).compactDisplayTitle)
|
|
} else {
|
|
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
messageText = presentationData.strings.PUSH_MESSAGE_FWDS_TEXT(Int32(item.messages.count))
|
|
}
|
|
} else if item.messages[0].groupingKey != nil {
|
|
var kind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.messages[0]), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId).key
|
|
for i in 1 ..< item.messages.count {
|
|
let nextKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.messages[i]), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
|
|
if kind != nextKind.key {
|
|
kind = .text
|
|
break
|
|
}
|
|
}
|
|
var isChannel = false
|
|
var isGroup = false
|
|
if let peer = peer as? TelegramChannel {
|
|
if case .broadcast = peer.info {
|
|
isChannel = true
|
|
} else {
|
|
isGroup = true
|
|
}
|
|
} else if item.messages[0].id.peerId.namespace == Namespaces.Peer.CloudGroup {
|
|
isGroup = true
|
|
}
|
|
title = EnginePeer(peer).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
|
|
if isChannel {
|
|
switch kind {
|
|
case .image:
|
|
messageText = presentationData.strings.PUSH_CHANNEL_MESSAGE_PHOTOS_TEXT(Int32(item.messages.count))
|
|
case .video:
|
|
messageText = presentationData.strings.PUSH_CHANNEL_MESSAGE_VIDEOS_TEXT(Int32(item.messages.count))
|
|
case .file:
|
|
messageText = presentationData.strings.PUSH_CHANNEL_MESSAGE_DOCS_TEXT(Int32(item.messages.count))
|
|
default:
|
|
messageText = presentationData.strings.PUSH_CHANNEL_MESSAGES_TEXT(Int32(item.messages.count))
|
|
}
|
|
} else if isGroup, var author = item.messages[0].author {
|
|
if let sourceReference = item.messages[0].sourceReference, let sourcePeer = item.messages[0].peers[sourceReference.messageId.peerId] {
|
|
author = sourcePeer
|
|
}
|
|
switch kind {
|
|
case .image:
|
|
messageText = presentationData.strings.PUSH_CHAT_MESSAGE_PHOTOS_TEXT(Int32(item.messages.count)).replacingOccurrences(of: "{author}", with: EnginePeer(author).compactDisplayTitle)
|
|
case .video:
|
|
messageText = presentationData.strings.PUSH_CHAT_MESSAGE_VIDEOS_TEXT(Int32(item.messages.count)).replacingOccurrences(of: "{author}", with: EnginePeer(author).compactDisplayTitle)
|
|
case .file:
|
|
messageText = presentationData.strings.PUSH_CHAT_MESSAGE_DOCS_TEXT(Int32(item.messages.count)).replacingOccurrences(of: "{author}", with: EnginePeer(author).compactDisplayTitle)
|
|
default:
|
|
messageText = presentationData.strings.PUSH_CHAT_MESSAGES_TEXT(Int32(item.messages.count)).replacingOccurrences(of: "{author}", with: EnginePeer(author).compactDisplayTitle)
|
|
}
|
|
} else {
|
|
switch kind {
|
|
case .image:
|
|
messageText = presentationData.strings.PUSH_MESSAGE_PHOTOS_TEXT(Int32(item.messages.count))
|
|
case .video:
|
|
messageText = presentationData.strings.PUSH_MESSAGE_VIDEOS_TEXT(Int32(item.messages.count))
|
|
case .file:
|
|
messageText = presentationData.strings.PUSH_MESSAGE_FILES_TEXT(Int32(item.messages.count))
|
|
default:
|
|
messageText = presentationData.strings.PUSH_MESSAGES_TEXT(Int32(item.messages.count))
|
|
}
|
|
}
|
|
} else {
|
|
messageText = ""
|
|
}
|
|
} else {
|
|
messageText = ""
|
|
}
|
|
|
|
if isReminder {
|
|
title = presentationData.strings.ScheduledMessages_ReminderNotification
|
|
} else if isScheduled, let currentTitle = title {
|
|
title = "📅 \(currentTitle)"
|
|
}
|
|
|
|
if let message = item.messages.first, let attribute = message.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title {
|
|
var isAction = false
|
|
for media in message.media {
|
|
if media is TelegramMediaAction {
|
|
isAction = true
|
|
break
|
|
}
|
|
}
|
|
if !isAction {
|
|
title = "\(currentTitle) 🔕"
|
|
}
|
|
}
|
|
|
|
var customEntities: [MessageTextEntity] = []
|
|
if item.messages[0].id.peerId.isTelegramNotifications || item.messages[0].id.peerId.isVerificationCodes {
|
|
let regex: NSRegularExpression?
|
|
if item.messages[0].id.peerId.isTelegramNotifications {
|
|
regex = telegramCodeRegex
|
|
} else {
|
|
regex = loginCodeRegex
|
|
}
|
|
if let matches = regex?.matches(in: item.messages[0].text, options: [], range: NSMakeRange(0, (item.messages[0].text as NSString).length)) {
|
|
if let first = matches.first {
|
|
customEntities.append(MessageTextEntity(range: first.range.location ..< first.range.location + first.range.length, type: .Spoiler))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !customEntities.isEmpty {
|
|
if messageEntities == nil {
|
|
messageEntities = customEntities
|
|
} else if var currentEntities = messageEntities {
|
|
currentEntities.append(contentsOf: customEntities)
|
|
messageEntities = customEntities
|
|
}
|
|
}
|
|
|
|
let textFont = compact ? Font.regular(15.0) : Font.regular(16.0)
|
|
let textColor = presentationData.theme.inAppNotification.primaryTextColor
|
|
var attributedMessageText: NSAttributedString
|
|
if let messageEntities = messageEntities {
|
|
attributedMessageText = stringWithAppliedEntities(messageText, entities: messageEntities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: item.messages.first)
|
|
} else {
|
|
attributedMessageText = NSAttributedString(string: messageText.replacingOccurrences(of: "\n\n", with: " "), font: textFont, textColor: textColor)
|
|
}
|
|
|
|
self.titleAttributedText = NSAttributedString(string: title ?? "", font: compact ? Font.semibold(15.0) : Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor)
|
|
|
|
let imageNodeLayout = self.imageNode.asyncLayout()
|
|
var applyImage: (() -> Void)?
|
|
if let imageDimensions = imageDimensions {
|
|
let boundingSize = CGSize(width: 55.0, height: 55.0)
|
|
var radius: CGFloat = 6.0
|
|
if isRound {
|
|
radius = floor(boundingSize.width / 2.0)
|
|
}
|
|
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
|
}
|
|
|
|
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
|
if let firstMessage = item.messages.first, let updatedMedia = updatedMedia, imageDimensions != nil {
|
|
if let image = updatedMedia as? TelegramMediaImage {
|
|
updateImageSignal = mediaGridMessagePhoto(account: item.context.account, userLocation: .peer(firstMessage.id.peerId), photoReference: .message(message: MessageReference(firstMessage), media: image))
|
|
} else if let file = updatedMedia as? TelegramMediaFile {
|
|
if file.isSticker {
|
|
updateImageSignal = chatMessageSticker(account: item.context.account, userLocation: .peer(firstMessage.id.peerId), file: file, small: true, fetched: true)
|
|
} else if file.isVideo {
|
|
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(firstMessage.id.peerId), videoReference: .message(message: MessageReference(firstMessage), media: file), autoFetchFullSizeThumbnail: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let applyImage = applyImage {
|
|
applyImage()
|
|
self.imageNode.isHidden = false
|
|
} else {
|
|
self.imageNode.isHidden = true
|
|
}
|
|
|
|
if let updateImageSignal = updateImageSignal {
|
|
self.imageNode.setSignal(updateImageSignal)
|
|
}
|
|
|
|
self.textAttributedText = attributedMessageText
|
|
|
|
if let width = self.validLayout {
|
|
let _ = self.updateLayout(width: width, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
|
self.validLayout = width
|
|
let compact = self.compact ?? false
|
|
|
|
let panelHeight: CGFloat = compact ? 64.0 : 74.0
|
|
let imageSize: CGSize = compact ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 54.0, height: 54.0)
|
|
let imageSpacing: CGFloat = compact ? 19.0 : 23.0
|
|
let leftInset: CGFloat = imageSize.width + imageSpacing
|
|
var rightInset: CGFloat = 8.0
|
|
|
|
if !self.imageNode.isHidden {
|
|
rightInset += imageSize.width + 8.0
|
|
}
|
|
|
|
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: 10.0, y: (panelHeight - imageSize.height) / 2.0), size: imageSize))
|
|
|
|
var titleInset: CGFloat = 0.0
|
|
if let image = self.titleIconNode.image {
|
|
titleInset += image.size.width + 4.0
|
|
}
|
|
|
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: self.titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - titleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
let _ = titleApply()
|
|
|
|
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: self.textAttributedText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets(), displaySpoilers: false))
|
|
let _ = titleApply()
|
|
|
|
if let item = self.item {
|
|
let theme = item.context.sharedContext.currentPresentationData.with({ $0 }).theme
|
|
let _ = textApply(TextNodeWithEntities.Arguments(
|
|
context: item.context,
|
|
cache: item.context.animationCache,
|
|
renderer: item.context.animationRenderer,
|
|
placeholderColor: theme.list.mediaPlaceholderColor,
|
|
attemptSynchronous: false
|
|
))
|
|
} else {
|
|
let _ = textApply(nil)
|
|
}
|
|
|
|
self.textNode.visibilityRect = CGRect.infinite
|
|
|
|
let textSpacing: CGFloat = 1.0
|
|
|
|
let titleFrame = CGRect(origin: CGPoint(x: leftInset + titleInset, y: 1.0 + floor((panelHeight - textLayout.size.height - titleLayout.size.height - textSpacing) / 2.0)), size: titleLayout.size)
|
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
|
|
|
if let image = self.titleIconNode.image {
|
|
transition.updateFrame(node: self.titleIconNode, frame: CGRect(origin: CGPoint(x: leftInset + 1.0, y: titleFrame.minY + 3.0), size: image.size))
|
|
}
|
|
|
|
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size)
|
|
transition.updateFrame(node: self.textNode.textNode, frame: textFrame)
|
|
|
|
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize))
|
|
|
|
if !textLayout.spoilers.isEmpty, let item = self.item {
|
|
let presentationData = item.context.sharedContext.currentPresentationData.with({ $0 })
|
|
let dustNode: InvisibleInkDustNode
|
|
if let current = self.dustNode {
|
|
dustNode = current
|
|
} else {
|
|
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
|
dustNode.isUserInteractionEnabled = false
|
|
self.dustNode = dustNode
|
|
self.insertSubnode(dustNode, aboveSubnode: self.textNode.textNode)
|
|
}
|
|
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
|
dustNode.update(size: dustNode.frame.size, color: presentationData.theme.inAppNotification.primaryTextColor, textColor: presentationData.theme.inAppNotification.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
|
} else if let dustNode = self.dustNode {
|
|
dustNode.removeFromSupernode()
|
|
self.dustNode = nil
|
|
}
|
|
|
|
return panelHeight
|
|
}
|
|
}
|