Swiftgram/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift
2024-10-25 11:24:06 +02:00

1939 lines
105 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TextFormat
import Markdown
import RadialStatusNode
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import SlotMachineAnimationNode
import AnimationUI
import StickerResources
import AvatarNode
import AccountContext
import AnimatedAvatarSetNode
import ComponentFlow
import EmojiStatusComponent
import TextNodeWithEntities
import BundleIconComponent
import AnimatedTextComponent
import ComponentDisplayAdapters
final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let presentationData: PresentationData
private let elevatedLayout: Bool
private let placementPosition: UndoOverlayController.Position
private var statusNode: RadialStatusNode?
private var didStartStatusNode: Bool = false
private let timerTextNode: ImmediateTextNode
private let avatarNode: AvatarNode?
private let iconNode: ASImageNode?
private var multiAvatarsNode: AnimatedAvatarSetNode?
private var multiAvatarsSize: CGSize?
private var iconImageSize: CGSize?
private let iconCheckNode: RadialStatusNode?
private let animationNode: AnimationNode?
private var animatedStickerNode: AnimatedStickerNode?
private var slotMachineNode: SlotMachineAnimationNode?
private var stillStickerNode: TransformImageNode?
private var stickerImageSize: CGSize?
private var stickerOffset: CGPoint?
private var emojiStatus: ComponentView<Empty>?
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNodeWithEntities
private var textComponent: ComponentView<Empty>?
private var animatedTextItems: [AnimatedTextComponent.Item]?
private let buttonNode: HighlightTrackingButtonNode
private let undoButtonTextNode: ImmediateTextNode
private let undoButtonNode: HighlightTrackingButtonNode
private let panelNode: ASDisplayNode
private let panelWrapperNode: ASDisplayNode
private let action: (UndoOverlayAction) -> Bool
private let dismiss: () -> Void
private var content: UndoOverlayContent
private let blurred: Bool
private let additionalView: UndoOverlayControllerAdditionalView?
private let effectView: UIView
private let animationBackgroundColor: UIColor
private var isTimeoutDisabled: Bool = false
private var originalRemainingSeconds: Double
private var remainingSeconds: Double
private let undoTextColor: UIColor
private var timer: SwiftSignalKit.Timer?
private var validLayout: ContainerViewLayout?
private var fetchResourceDisposable: Disposable?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)?, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
self.presentationData = presentationData
self.elevatedLayout = elevatedLayout
self.placementPosition = placementPosition
self.blurred = blurred
self.content = content
self.additionalView = additionalView?()
self.action = action
self.dismiss = dismiss
self.timerTextNode = ImmediateTextNode()
self.timerTextNode.displaysAsynchronously = false
self.titleNode = ImmediateTextNode()
self.titleNode.layer.anchorPoint = CGPoint()
self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 0
self.textNode = ImmediateTextNodeWithEntities()
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0
self.buttonNode = HighlightTrackingButtonNode()
var displayUndo = true
var undoText = presentationData.strings.Undo_Undo
var undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
if presentationData.theme.overallDarkAppearance {
self.animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
} else {
self.animationBackgroundColor = UIColor(rgb: 0x474747)
}
var isUserInteractionEnabled = false
switch content {
case let .removedChat(title, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
if let text {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
} else {
self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
displayUndo = true
self.originalRemainingSeconds = 5
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
case let .archivedChat(_, title, text, undo):
self.avatarNode = nil
if undo {
self.iconNode = ASImageNode()
self.iconNode?.displayWithoutProcessing = true
self.iconNode?.displaysAsynchronously = false
self.iconNode?.image = UIImage(bundleImageName: "Chat List/ArchivedUndoIcon")
self.iconCheckNode = RadialStatusNode(backgroundNodeColor: .clear)
self.iconCheckNode?.frame = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0)
self.animationNode = nil
} else {
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
}
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
displayUndo = undo
self.originalRemainingSeconds = 5
case let .hidArchive(title, text, undo):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_archiveswipe", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = undo
self.originalRemainingSeconds = 3
case let .revealedArchive(title, text, undo):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = undo
self.originalRemainingSeconds = 3
case let .autoDelete(isOn, title, text, customUndoText):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: isOn ? "anim_autoremove_on" : "anim_autoremove_off", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
if let customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = 4.5
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .succeed(text, timeout, customUndoText):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
if let customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = timeout ?? 3
case let .info(title, text, timeout, customUndoText):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 10
if let customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
if let timeout {
self.originalRemainingSeconds = timeout
} else {
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
}
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .actionSucceeded(title, text, cancel, destructive):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
if destructive {
undoTextColor = UIColor(rgb: 0xff7b74)
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
if let title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
self.textNode.attributedText = attributedText
if let cancel {
displayUndo = true
undoText = cancel
} else {
displayUndo = false
}
self.originalRemainingSeconds = 5
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .linkCopied(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_linkcopied", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .banned(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
case let .importedMessage(text):
self.avatarNode = nil
self.iconNode = ASImageNode()
self.iconNode?.displayWithoutProcessing = true
self.iconNode?.displaysAsynchronously = false
self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/ImportedMessageTooltipIcon"), color: .white)
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = false
self.originalRemainingSeconds = 5
case let .chatAddedToFolder(chatTitle, folderTitle):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let formattedString = presentationData.strings.ChatList_AddedToFolderTooltip(chatTitle, folderTitle)
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
for range in formattedString.ranges {
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
}
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .chatRemovedFromFolder(chatTitle, folderTitle):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let formattedString = presentationData.strings.ChatList_RemovedFromFolderTooltip(chatTitle, folderTitle)
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
for range in formattedString.ranges {
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
}
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .paymentSent(currencyValue, itemTitle):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_payment", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let formattedString = presentationData.strings.Checkout_SuccessfulTooltip(currencyValue, itemTitle)
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
for range in formattedString.ranges {
string.addAttribute(.font, value: Font.semibold(14.0), range: range.range)
}
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .starsSent(_, title, textItems):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
let emojiStatus = ComponentView<Empty>()
self.emojiStatus = emojiStatus
let _ = emojiStatus.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: "Premium/Stars/StarLarge",
tintColor: nil
)),
environment: {},
containerSize: imageBoundingSize
)
self.stickerImageSize = imageBoundingSize
self.animatedTextItems = textItems
displayUndo = true
self.originalRemainingSeconds = 4.9
isUserInteractionEnabled = true
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
case let .messagesUnpinned(title, text, undo, isHidden):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: isHidden ? "anim_message_hidepin" : "anim_message_unpin", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
if !text.isEmpty {
self.textNode.attributedText = attributedText
}
displayUndo = undo
self.originalRemainingSeconds = 5
case let .emoji(name, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = DefaultAnimatedStickerNodeImpl()
self.animatedStickerNode?.visibility = true
self.animatedStickerNode?.setup(source: AnimatedStickerNodeLocalFileSource(name: name), width: 100, height: 100, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 10
displayUndo = false
self.originalRemainingSeconds = 5
case let .swipeToReply(title, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_swipereply", colors: [:], scale: 1.0)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
case let .stickersModified(title, text, undo, info, topItem, context):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let stillStickerNode = TransformImageNode()
self.stillStickerNode = stillStickerNode
enum StickerPackThumbnailItem {
case still(TelegramMediaImageRepresentation)
case animated(EngineMediaResource, PixelDimensions, Bool)
}
var thumbnailItem: StickerPackThumbnailItem?
var resourceReference: MediaResourceReference?
if let thumbnail = info.thumbnail {
if thumbnail.typeHint != .generic {
thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video)
} else {
thumbnailItem = .still(thumbnail)
}
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
} else if let item = topItem {
if item.file.isAnimatedSticker || item.file.isVideoSticker {
thumbnailItem = .animated(EngineMediaResource(item.file.resource), item.file.dimensions ?? PixelDimensions(width: 512, height: 512), item.file.isVideoSticker)
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource)
}
}
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case let .still(representation):
let stillImageSize = representation.dimensions.cgSize.aspectFitted(imageBoundingSize)
self.stickerImageSize = stillImageSize
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: representation.resource)
case let .animated(resource, dimensions, _):
self.stickerImageSize = dimensions.cgSize.aspectFitted(imageBoundingSize)
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true)
}
if let resourceReference = resourceReference {
updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference)
|> mapError { _ -> EngineMediaResource.Fetch.Error in
return .generic
}
|> ignoreValues
}
} else {
updatedImageSignal = .single({ _ in return nil })
updatedFetchSignal = .complete()
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = undo
self.originalRemainingSeconds = 2
if let updatedFetchSignal = updatedFetchSignal {
self.fetchResourceDisposable = updatedFetchSignal.start()
}
if let updatedImageSignal = updatedImageSignal {
stillStickerNode.setSignal(updatedImageSignal)
}
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case .still:
break
case let .animated(resource, _, isVideo):
let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
self.animatedStickerNode = animatedStickerNode
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: isVideo), width: 80, height: 80, mode: .direct(cachePathPrefix: nil))
}
}
case let .dice(dice, context, text, action):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
if let action = action {
displayUndo = true
undoText = action
} else {
displayUndo = false
}
self.originalRemainingSeconds = 5
self.stickerImageSize = CGSize(width: 42.0, height: 42.0)
switch dice.emoji {
case "🎲":
self.stickerOffset = CGPoint(x: 0.0, y: -7.0)
default:
break
}
if dice.emoji == "🎰" {
let slotMachineNode = SlotMachineAnimationNode(account: context.account, size: CGSize(width: 42.0, height: 42.0))
self.slotMachineNode = slotMachineNode
slotMachineNode.setState(.rolling)
if let value = dice.value {
slotMachineNode.setState(.value(value, true))
}
} else {
let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
self.animatedStickerNode = animatedStickerNode
let _ = (context.engine.stickers.loadedStickerPack(reference: .dice(dice.emoji), forceActualized: false)
|> deliverOnMainQueue).start(next: { stickerPack in
if let value = dice.value {
switch stickerPack {
case let .result(_, items, _):
let item = items[Int(value)]
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file.resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
default:
break
}
}
})
}
case let .setProximityAlert(title, text, cancelled):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: cancelled ? "anim_proximity_cancelled" : "anim_proximity_set", colors: [:], scale: 0.45)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
if !text.isEmpty {
self.textNode.attributedText = attributedText
}
displayUndo = false
self.originalRemainingSeconds = 3
case let .invitedToVoiceChat(context, peer, title, text, action, duration):
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title ?? "", font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
if let action = action {
displayUndo = true
undoText = action
} else {
displayUndo = false
}
self.originalRemainingSeconds = duration
case let .audioRate(rate, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
let animationName: String
if rate == .infinity {
animationName = "anim_voicefast"
} else if rate == -.infinity {
animationName = "anim_voiceslow"
} else if rate == 1.5 {
animationName = "anim_voice1_5x"
} else if rate == 2.0 {
animationName = "anim_voice2x"
} else {
animationName = "anim_voice1x"
}
self.animationNode = AnimationNode(animation: animationName, colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .forward(savedMessages, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: savedMessages ? "anim_savedmessages" : "anim_forward", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold: MarkdownAttributeSet
var link = body
if savedMessages {
bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0), additionalAttributes: ["URL": ""])
link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
} else {
bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
}
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
if savedMessages {
isUserInteractionEnabled = true
}
displayUndo = false
self.originalRemainingSeconds = 3
case let .gigagroupConversion(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_gigagroup", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .linkRevoked(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_linkrevoked", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .voiceChatRecording(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_vcrecord", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .voiceChatFlag(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_vcflag", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .voiceChatCanSpeak(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_vcspeak", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .sticker(context, file, loop, title, text, customUndoText, _):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let stillStickerNode = TransformImageNode()
self.stillStickerNode = stillStickerNode
enum StickerThumbnailItem {
case still(TelegramMediaImageRepresentation)
case animated(EngineMediaResource)
}
var thumbnailItem: StickerThumbnailItem?
var resourceReference: MediaResourceReference?
if file.isAnimatedSticker {
thumbnailItem = .animated(EngineMediaResource(file.resource))
resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)
} else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: true) as? TelegramMediaResource {
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: resource)
}
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case let .still(representation):
let stillImageSize = representation.dimensions.cgSize.aspectFitted(imageBoundingSize)
self.stickerImageSize = stillImageSize
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: representation.resource)
case let .animated(resource):
self.stickerImageSize = imageBoundingSize
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true)
}
if let resourceReference = resourceReference {
updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference)
|> mapError { _ -> EngineMediaResource.Fetch.Error in
return .generic
}
|> ignoreValues
}
} else {
updatedImageSignal = .single({ _ in return nil })
updatedFetchSignal = .complete()
}
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
if text.contains("](") {
isUserInteractionEnabled = true
}
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
if let updatedFetchSignal = updatedFetchSignal {
self.fetchResourceDisposable = updatedFetchSignal.start()
}
if let updatedImageSignal = updatedImageSignal {
stillStickerNode.setSignal(updatedImageSignal)
}
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case .still:
break
case let .animated(resource):
let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
self.animatedStickerNode = animatedStickerNode
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: file.isVideoSticker), width: 80, height: 80, playbackMode: loop ? .loop : .once, mode: .cached)
}
}
case let .customEmoji(context, file, loop, title, text, customUndoText, _):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
let emojiStatus = ComponentView<Empty>()
self.emojiStatus = emojiStatus
let _ = emojiStatus.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: .animation(
content: .file(file: file),
size: imageBoundingSize,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
themeColor: .white,
loopMode: loop ? .forever : .count(1)
),
isVisibleForAnimations: true,
useSharedAnimation: false,
action: nil
)),
environment: {},
containerSize: imageBoundingSize
)
self.stickerImageSize = imageBoundingSize
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
if text.contains("](") {
isUserInteractionEnabled = true
}
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
case let .copy(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_copy", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
case let .mediaSaved(text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
self.animatedStickerNode = animatedStickerNode
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_savemedia"), width: 80, height: 80, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
animatedStickerNode.visibility = true
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .inviteRequestSent(title, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_inviterequest", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
case let .notificationSoundAdded(title, text, action):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_notificationsound", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
displayUndo = false
self.originalRemainingSeconds = 5
if let action = action {
self.textNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
}
self.textNode.tapAttributeAction = { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
action()
}
}
}
case let .universal(animation, scale, colors, title, text, customUndoText, timeout):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: animation, colors: colors, scale: scale)
self.animatedStickerNode = nil
if let title = title, text.isEmpty {
self.titleNode.attributedText = nil
let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
} else {
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
}
if text.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
case let .universalImage(image, size, title, text, customUndoText, timeout):
self.iconNode = ASImageNode()
self.iconNode?.displayWithoutProcessing = true
self.iconNode?.displaysAsynchronously = false
self.iconNode?.image = image
self.iconImageSize = size
self.avatarNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
if let title = title, text.isEmpty {
self.titleNode.attributedText = nil
let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
} else {
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
}
if text.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
case let .premiumPaywall(title, text, customUndoText, timeout, linkAction):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "PremiumStar", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
if let title = title, text.isEmpty {
self.titleNode.attributedText = nil
let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
} else {
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
}
if let linkAction {
self.textNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
}
self.textNode.tapAttributeAction = { attributes, _ in
if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
linkAction(value)
}
}
}
if text.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
case let .image(image, title, text, round, customUndoText):
self.avatarNode = nil
self.iconNode = ASImageNode()
self.iconNode?.clipsToBounds = true
self.iconNode?.contentMode = .scaleAspectFill
self.iconNode?.image = image
self.iconNode?.cornerRadius = round ? 16.0 : 4.0
self.iconImageSize = image.size.aspectFitted(CGSize(width: 128.0, height: 32.0))
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = displayUndo ? 5 : 3
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .peers(context, peers, title, text, customUndoText):
self.avatarNode = nil
let multiAvatarsNode = AnimatedAvatarSetNode()
self.multiAvatarsNode = multiAvatarsNode
let avatarsContext = AnimatedAvatarSetContext()
self.multiAvatarsSize = multiAvatarsNode.update(context: context, content: avatarsContext.update(peers: peers, animated: false), itemSize: CGSize(width: 28.0, height: 28.0), animated: false, synchronousLoad: false)
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
if text.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
case let .messageTagged(context, isSingleMessage, customEmoji, isBuiltinReaction, customUndoText):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_savedmessages", colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let rawText = isSingleMessage ? presentationData.strings.Chat_ToastMessageTagged_Text(".") : presentationData.strings.Chat_ToastMessagesTagged_Text(".")
let attributedText = NSMutableAttributedString(string: rawText.string, font: Font.regular(14.0), textColor: .white)
for range in rawText.ranges {
attributedText.addAttributes([ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: customEmoji.fileId.id, file: customEmoji, custom: nil)], range: range.range)
}
self.textNode.customItemLayout = { size, _ in
if isBuiltinReaction {
return CGSize(width: size.width * 2.0, height: size.height * 2.0)
}
return size
}
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
self.textNode.visibility = true
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 3
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
isUserInteractionEnabled = true
} else {
displayUndo = false
}
}
self.remainingSeconds = self.originalRemainingSeconds
self.undoTextColor = undoTextColor
self.undoButtonTextNode = ImmediateTextNode()
self.undoButtonTextNode.displaysAsynchronously = false
self.undoButtonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor)
self.undoButtonNode = HighlightTrackingButtonNode()
self.panelNode = ASDisplayNode()
if presentationData.theme.overallDarkAppearance && !self.blurred {
self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
} else {
self.panelNode.backgroundColor = .clear
}
self.panelNode.clipsToBounds = true
self.panelNode.cornerRadius = 14.0
self.panelWrapperNode = ASDisplayNode()
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
super.init()
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .starsSent:
self.panelWrapperNode.addSubnode(self.timerTextNode)
if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal,. universalImage, .premiumPaywall, .peers, .messageTagged:
if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
case .sticker, .customEmoji:
self.isUserInteractionEnabled = displayUndo
case .dice:
self.panelWrapperNode.clipsToBounds = true
case .info:
if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
}
if isUserInteractionEnabled {
self.isUserInteractionEnabled = true
}
self.titleNode.isUserInteractionEnabled = false
self.textNode.isUserInteractionEnabled = self.textNode.tapAttributeAction != nil
self.iconNode?.isUserInteractionEnabled = false
self.animationNode?.isUserInteractionEnabled = false
self.iconCheckNode?.isUserInteractionEnabled = false
self.avatarNode?.isUserInteractionEnabled = false
self.multiAvatarsNode?.isUserInteractionEnabled = false
self.slotMachineNode?.isUserInteractionEnabled = false
self.animatedStickerNode?.isUserInteractionEnabled = false
self.statusNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconCheckNode.flatMap(self.panelWrapperNode.addSubnode)
self.animationNode.flatMap(self.panelWrapperNode.addSubnode)
self.stillStickerNode.flatMap(self.panelWrapperNode.addSubnode)
if let emojiStatusView = self.emojiStatus?.view {
self.panelWrapperNode.view.addSubview(emojiStatusView)
}
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
self.multiAvatarsNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.buttonNode)
self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode)
if displayUndo {
self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
self.panelWrapperNode.addSubnode(self.undoButtonNode)
}
self.addSubnode(self.panelNode)
self.addSubnode(self.panelWrapperNode)
self.undoButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.undoButtonTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.undoButtonTextNode.alpha = 0.4
} else {
strongSelf.undoButtonTextNode.alpha = 1.0
strongSelf.undoButtonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.undoButtonNode.addTarget(self, action: #selector(self.undoButtonPressed), forControlEvents: .touchUpInside)
self.animatedStickerNode?.started = { [weak self] in
self?.stillStickerNode?.isHidden = true
}
if let additionalView = self.additionalView {
additionalView.interaction = UndoOverlayControllerAdditionalViewInteraction(disableTimeout: { [weak self] in
guard let self else {
return
}
self.isTimeoutDisabled = true
self.timer?.invalidate()
self.remainingSeconds = self.originalRemainingSeconds
self.checkTimer()
}, dismiss: { [weak self] in
guard let self else {
return
}
self.dismiss()
})
self.view.addSubview(additionalView)
}
}
deinit {
self.fetchResourceDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.panelNode.layer.cornerCurve = .continuous
}
self.panelNode.view.addSubview(self.effectView)
if self.additionalView != nil {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.dismiss()
}
}
@objc private func buttonPressed() {
if self.action(.info) {
self.dismiss()
}
}
@objc private func undoButtonPressed() {
switch self.content {
case let .sticker(_, _, _, _, _, _, customAction):
if let customAction = customAction {
customAction()
} else {
let _ = self.action(.undo)
}
case let .customEmoji(_, _, _, _, _, _, customAction):
if let customAction = customAction {
customAction()
} else {
let _ = self.action(.undo)
}
default:
let _ = self.action(.undo)
}
self.dismiss()
}
private func checkTimer() {
let previousRemainingSeconds = Int(self.remainingSeconds)
if self.timer != nil {
self.remainingSeconds -= 0.5
}
if self.remainingSeconds <= 0.0 {
let _ = self.action(.commit)
self.dismiss()
} else {
let remainingSecondsString = "\(Int(self.remainingSeconds))"
if Int(self.remainingSeconds) != previousRemainingSeconds || self.timerTextNode.attributedText?.string != remainingSecondsString {
if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() {
self.panelNode.view.insertSubview(snapshot, aboveSubview: self.timerTextNode.view)
snapshot.frame = self.timerTextNode.frame
self.timerTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
self.timerTextNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -10.0), to: CGPoint(), duration: 0.12, removeOnCompletion: false, additive: true)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false)
snapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 10.0), duration: 0.12, removeOnCompletion: false, additive: true, completion: { [weak snapshot] _ in
snapshot?.removeFromSuperview()
})
}
let timerColor: UIColor
if case .starsSent = self.content {
timerColor = self.undoTextColor
} else {
timerColor = .white
}
self.timerTextNode.attributedText = NSAttributedString(string: remainingSecondsString, font: Font.regular(16.0), textColor: timerColor)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
}
}
if !self.isTimeoutDisabled {
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: false, completion: { [weak self] in
self?.checkTimer()
}, queue: .mainQueue())
self.timer = timer
timer.start()
}
}
}
func renewWithCurrentContent() {
self.timer?.invalidate()
self.timer = nil
self.remainingSeconds = self.originalRemainingSeconds
self.didStartStatusNode = false
self.checkTimer()
}
func updateContent(_ content: UndoOverlayContent) {
self.content = content
var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
var transition: ContainedViewLayoutTransition = .immediate
switch content {
case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _):
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
if let title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
self.textNode.attributedText = attributedText
case let .image(image, title, text, _, _):
self.iconNode?.image = image
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
self.renewWithCurrentContent()
case let .actionSucceeded(title, text, _, destructive):
if destructive {
undoTextColor = UIColor(rgb: 0xff7b74)
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
if let title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
self.textNode.attributedText = attributedText
case let .starsSent(_, title, textItems):
self.animatedTextItems = textItems
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.renewWithCurrentContent()
transition = .animated(duration: 0.1, curve: .easeInOut)
default:
break
}
if let validLayout = self.validLayout {
self.containerLayoutUpdated(layout: validLayout, transition: transition)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
var preferredSize: CGSize?
var verticalOffset: CGFloat = 0.0
if let animationNode = self.animationNode, let iconSize = animationNode.preferredSize() {
if case .messagesUnpinned = self.content {
let factor: CGFloat = 0.5
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else if case .linkCopied = self.content {
let factor: CGFloat = 0.08
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else if case .banned = self.content {
let factor: CGFloat = 0.08
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else if case .autoDelete = self.content {
let factor: CGFloat = 0.07
verticalOffset = -3.0
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else if case .paymentSent = self.content {
let factor: CGFloat = 0.08
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else {
preferredSize = iconSize
}
}
var leftInset: CGFloat = 50.0
if let iconImageSize = self.iconImageSize {
leftInset = 9.0 + iconImageSize.width + 9.0
} else if let iconSize = preferredSize {
if iconSize.width > leftInset {
leftInset = iconSize.width - 8.0
}
} else if let multiAvatarsSize = self.multiAvatarsSize {
leftInset = 13.0 + multiAvatarsSize.width + 20.0
}
let rightInset: CGFloat = 16.0
var contentHeight: CGFloat = 20.0
let margin: CGFloat = 12.0
let leftMargin = margin + layout.insets(options: []).left
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
let buttonMinX: CGFloat
if self.undoButtonNode.supernode != nil {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - leftMargin * 2.0
} else {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
}
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
let maxTextSize = CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude)
let textSize: CGSize
if let animatedTextItems = self.animatedTextItems {
let textComponent: ComponentView<Empty>
if let current = self.textComponent {
textComponent = current
} else {
textComponent = ComponentView()
self.textComponent = textComponent
}
textSize = textComponent.update(
transition: ComponentTransition(transition),
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(14.0),
color: .white,
items: animatedTextItems
)),
environment: {},
containerSize: maxTextSize
)
if let textComponentView = textComponent.view {
if textComponentView.superview == nil {
textComponentView.layer.anchorPoint = CGPoint()
self.panelWrapperNode.view.addSubview(textComponentView)
}
}
} else {
if let textComponentView = self.textComponent?.view {
self.textComponent = nil
textComponentView.removeFromSuperview()
}
textSize = self.textNode.updateLayout(maxTextSize)
}
if !titleSize.width.isZero {
contentHeight += titleSize.height + 1.0
}
contentHeight += textSize.height
contentHeight = max(49.0, contentHeight)
var insets = layout.insets(options: [.input])
switch self.placementPosition {
case .top:
break
case .bottom:
if self.elevatedLayout {
insets.bottom += 49.0
}
}
var panelFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
var panelWrapperFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
if case .top = self.placementPosition {
var topInset = insets.top
if topInset.isZero {
topInset = layout.statusBarHeight ?? 44.0
}
panelFrame.origin.y = topInset + margin
panelWrapperFrame.origin.y = topInset + margin
}
transition.updateFrame(node: self.panelNode, frame: panelFrame)
transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
var buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
var undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight))
if case .starsSent = self.content {
let buttonOffset: CGFloat = -34.0
undoButtonFrame.origin.x += buttonOffset
buttonTextFrame.origin.x += buttonOffset
}
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
self.undoButtonNode.frame = undoButtonFrame
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.undoButtonNode.supernode == nil ? panelFrame.width : undoButtonFrame.minX, height: contentHeight))
var textContentHeight = textSize.height
var textOffset: CGFloat = 0.0
if !titleSize.width.isZero {
textContentHeight += titleSize.height + 1.0
textOffset += titleSize.height + 1.0
}
let textContentOrigin = floor((contentHeight - textContentHeight) / 2.0)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize)
transition.updatePosition(node: self.titleNode, position: titleFrame.origin)
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize)
if let textComponentView = self.textComponent?.view {
transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
} else {
transition.updateFrame(node: self.textNode, frame: textFrame)
}
if let iconNode = self.iconNode {
let iconSize: CGSize
if let size = self.iconImageSize {
iconSize = size
} else if let size = iconNode.image?.size {
iconSize = size
} else {
iconSize = CGSize()
}
let iconFrame: CGRect
if self.iconImageSize != nil {
iconFrame = CGRect(origin: CGPoint(x: 9.0, y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
} else {
iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
}
transition.updateFrame(node: iconNode, frame: iconFrame)
if let iconCheckNode = self.iconCheckNode {
let statusSize: CGFloat = iconCheckNode.frame.width
var offset: CGFloat = 0.0
if statusSize < 30.0 {
offset = 3.0
}
transition.updateFrame(node: iconCheckNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floor((iconFrame.width - statusSize) / 2.0), y: iconFrame.minY + floor((iconFrame.height - statusSize) / 2.0) + offset), size: CGSize(width: statusSize, height: statusSize)))
}
}
if let animationNode = self.animationNode, let iconSize = preferredSize {
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
transition.updateFrame(node: animationNode, frame: iconFrame)
}
if let stickerImageSize = self.stickerImageSize {
let iconSize = stickerImageSize
var iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
if let stickerOffset = self.stickerOffset {
iconFrame = iconFrame.offsetBy(dx: stickerOffset.x, dy: stickerOffset.y)
}
if let stillStickerNode = self.stillStickerNode {
let makeImageLayout = stillStickerNode.asyncLayout()
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stickerImageSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
let _ = imageApply()
transition.updateFrame(node: stillStickerNode, frame: iconFrame)
}
if let emojiStatusView = self.emojiStatus?.view {
transition.updateFrame(view: emojiStatusView, frame: iconFrame)
}
if let animatedStickerNode = self.animatedStickerNode {
animatedStickerNode.updateLayout(size: iconFrame.size)
transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
} else if let slotMachineNode = self.slotMachineNode {
transition.updateFrame(node: slotMachineNode, frame: iconFrame)
}
} else if let animatedStickerNode = self.animatedStickerNode {
let iconSize = CGSize(width: 32.0, height: 32.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
animatedStickerNode.updateLayout(size: iconFrame.size)
transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
} else if let slotMachineNode = self.slotMachineNode {
let iconSize = CGSize(width: 32.0, height: 32.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
transition.updateFrame(node: slotMachineNode, frame: iconFrame)
}
let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
if case .starsSent = self.content {
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset + floor((rightInset - timerTextSize.width) * 0.5) - 46.0)), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
} else {
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
}
if let statusNode = self.statusNode {
let statusSize: CGFloat = 30.0
var statusFrame = CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))
if case .starsSent = self.content {
statusFrame.origin.x = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - statusSize - 23.0
}
transition.updateFrame(node: statusNode, frame: statusFrame)
if !self.didStartStatusNode {
let statusColor: UIColor
if case .starsSent = self.content {
statusColor = self.undoTextColor
} else {
statusColor = .white
}
statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
self.didStartStatusNode = true
}
}
if let avatarNode = self.avatarNode {
let avatarSize: CGFloat = 30.0
transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - avatarSize) / 2.0), y: floor((contentHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)))
}
if let multiAvatarsNode = self.multiAvatarsNode, let multiAvatarsSize = self.multiAvatarsSize {
let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: floor((contentHeight - multiAvatarsSize.height) / 2.0) + verticalOffset), size: multiAvatarsSize)
transition.updateFrame(node: multiAvatarsNode, frame: avatarsFrame)
}
if let additionalView = self.additionalView {
let additionalViewFrame = CGRect(origin: CGPoint(x: 0.0, y: panelWrapperFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height - insets.bottom - panelWrapperFrame.maxY))
transition.updateFrame(view: additionalView, frame: additionalViewFrame)
additionalView.update(size: additionalViewFrame.size, transition: transition)
}
}
func animateIn(asReplacement: Bool) {
if asReplacement {
let offset: CGFloat
switch self.placementPosition {
case .top:
offset = -self.panelWrapperNode.frame.maxY
case.bottom:
offset = self.bounds.height - self.panelWrapperNode.frame.minY
}
self.panelWrapperNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.35, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: nil)
self.panelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.35, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: nil)
} else {
self.panelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.panelWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.panelNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.panelWrapperNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
if let iconCheckNode = self.iconCheckNode, self.iconNode != nil {
Queue.mainQueue().after(0.2, { [weak iconCheckNode] in
iconCheckNode?.transitionToState(.check(self.animationBackgroundColor), completion: {})
})
}
if let animationNode = self.animationNode {
Queue.mainQueue().after(0.2, { [weak animationNode] in
animationNode?.play()
})
}
self.animatedStickerNode?.visibility = true
if let additionalView = self.additionalView {
additionalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
self.checkTimer()
}
var dismissed = false
func animateOut(completion: @escaping () -> Void) {
guard !self.dismissed else {
return
}
self.dismissed = true
self.panelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { _ in })
self.panelWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) { _ in
completion()
}
self.panelNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.panelWrapperNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
if let additionalView = self.additionalView {
additionalView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
}
}
func animateOutWithReplacement(completion: @escaping () -> Void) {
self.panelWrapperNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false, completion: { _ in
completion()
})
self.panelWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.panelNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false, completion: { _ in
completion()
})
self.panelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let additionalView = self.additionalView {
if let result = additionalView.hitTest(self.view.convert(point, to: additionalView), with: event) {
return result
}
}
if !self.panelNode.frame.insetBy(dx: -60.0, dy: 0.0).contains(point) {
if self.additionalView != nil && self.isTimeoutDisabled {
} else {
return nil
}
}
let result = super.hitTest(point, with: event)
if result == self {
if self.additionalView != nil && self.isTimeoutDisabled {
return self.view
}
}
return result
}
}