mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1169 lines
67 KiB
Swift
1169 lines
67 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import TextFormat
|
|
import Markdown
|
|
import RadialStatusNode
|
|
import AppBundle
|
|
import AnimatedStickerNode
|
|
import TelegramAnimatedStickerNode
|
|
import SlotMachineAnimationNode
|
|
import AnimationUI
|
|
import StickerResources
|
|
import AvatarNode
|
|
import AccountContext
|
|
|
|
final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|
private let elevatedLayout: Bool
|
|
private var statusNode: RadialStatusNode?
|
|
private let timerTextNode: ImmediateTextNode
|
|
private let avatarNode: AvatarNode?
|
|
private let iconNode: ASImageNode?
|
|
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 let titleNode: ImmediateTextNode
|
|
private let textNode: ImmediateTextNode
|
|
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 effectView: UIView
|
|
|
|
private let animationBackgroundColor: UIColor
|
|
|
|
private var originalRemainingSeconds: Double
|
|
private var remainingSeconds: Double
|
|
private var timer: SwiftSignalKit.Timer?
|
|
|
|
private var validLayout: ContainerViewLayout?
|
|
|
|
private var fetchResourceDisposable: Disposable?
|
|
|
|
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
|
|
self.elevatedLayout = elevatedLayout
|
|
self.content = content
|
|
|
|
self.action = action
|
|
self.dismiss = dismiss
|
|
|
|
self.timerTextNode = ImmediateTextNode()
|
|
self.timerTextNode.displaysAsynchronously = false
|
|
|
|
self.titleNode = ImmediateTextNode()
|
|
self.titleNode.displaysAsynchronously = false
|
|
self.titleNode.maximumNumberOfLines = 0
|
|
|
|
self.textNode = ImmediateTextNode()
|
|
self.textNode.displaysAsynchronously = false
|
|
self.textNode.maximumNumberOfLines = 0
|
|
|
|
self.buttonNode = HighlightTrackingButtonNode()
|
|
|
|
var displayUndo = true
|
|
var undoText = presentationData.strings.Undo_Undo
|
|
var undoTextColor = UIColor(rgb: 0x5ac8fa)
|
|
undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08)
|
|
|
|
if presentationData.theme.overallDarkAppearance {
|
|
self.animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
|
} else {
|
|
self.animationBackgroundColor = UIColor(rgb: 0x474747)
|
|
}
|
|
|
|
switch content {
|
|
case let .removedChat(text):
|
|
self.avatarNode = nil
|
|
self.iconNode = nil
|
|
self.iconCheckNode = nil
|
|
self.animationNode = nil
|
|
self.animatedStickerNode = nil
|
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(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)
|
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
|
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):
|
|
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)
|
|
}
|
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
|
displayUndo = false
|
|
self.originalRemainingSeconds = 3.5
|
|
case let .succeed(text):
|
|
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
|
|
displayUndo = false
|
|
self.originalRemainingSeconds = 3
|
|
case let .info(title, text):
|
|
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 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 = Double(max(5, min(8, text.count / 14)))
|
|
case let .actionSucceeded(title, text, cancel):
|
|
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
|
|
|
|
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)
|
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
|
self.textNode.attributedText = attributedText
|
|
displayUndo = true
|
|
undoText = cancel
|
|
self.originalRemainingSeconds = 5
|
|
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 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 .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 .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 = AnimatedStickerNode()
|
|
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 = 2
|
|
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 info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) {
|
|
thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, info.flags.contains(.isVideo))
|
|
} 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))
|
|
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, 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 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 = 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 = AnimatedStickerNode()
|
|
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 = AnimatedStickerNode()
|
|
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, text):
|
|
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
|
self.iconNode = nil
|
|
self.iconCheckNode = nil
|
|
self.animationNode = nil
|
|
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.textNode.attributedText = attributedText
|
|
|
|
self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
|
|
|
|
displayUndo = false
|
|
self.originalRemainingSeconds = 3
|
|
case let .audioRate(slowdown, text):
|
|
self.avatarNode = nil
|
|
self.iconNode = nil
|
|
self.iconCheckNode = nil
|
|
self.animationNode = AnimationNode(animation: slowdown ? "anim_voicespeedstop" : "anim_voicespeed", 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(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 .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, text):
|
|
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))
|
|
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, reference: resourceReference)
|
|
|> mapError { _ -> EngineMediaResource.Fetch.Error in
|
|
return .generic
|
|
}
|
|
|> ignoreValues
|
|
}
|
|
} else {
|
|
updatedImageSignal = .single({ _ in return nil })
|
|
updatedFetchSignal = .complete()
|
|
}
|
|
|
|
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
|
|
|
|
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 = AnimatedStickerNode()
|
|
self.animatedStickerNode = animatedStickerNode
|
|
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: file.isVideoSticker), width: 80, height: 80, mode: .cached)
|
|
}
|
|
}
|
|
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 = AnimatedStickerNode()
|
|
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 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 .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: { _ in return nil }), textAlignment: .natural)
|
|
self.textNode.attributedText = attributedText
|
|
|
|
self.textNode.maximumNumberOfLines = 5
|
|
displayUndo = false
|
|
self.originalRemainingSeconds = 5
|
|
|
|
if let action = action {
|
|
self.textNode.tapAttributeAction = { attributes, _ in
|
|
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
|
action()
|
|
}
|
|
}
|
|
}
|
|
case let .image(image, text):
|
|
self.avatarNode = nil
|
|
self.iconNode = ASImageNode()
|
|
self.iconNode?.clipsToBounds = true
|
|
self.iconNode?.contentMode = .scaleAspectFill
|
|
self.iconNode?.image = image
|
|
self.iconNode?.cornerRadius = 4.0
|
|
self.iconImageSize = CGSize(width: 32.0, height: 32.0)
|
|
self.iconCheckNode = nil
|
|
self.animationNode = nil
|
|
self.animatedStickerNode = nil
|
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
|
displayUndo = true
|
|
self.originalRemainingSeconds = 5
|
|
}
|
|
|
|
self.remainingSeconds = self.originalRemainingSeconds
|
|
|
|
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.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
|
} else {
|
|
self.panelNode.backgroundColor = .clear
|
|
}
|
|
self.panelNode.clipsToBounds = true
|
|
self.panelNode.cornerRadius = 9.0
|
|
|
|
self.panelWrapperNode = ASDisplayNode()
|
|
|
|
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
|
|
|
super.init()
|
|
|
|
switch content {
|
|
case .removedChat:
|
|
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
|
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, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded:
|
|
break
|
|
case .dice:
|
|
self.panelWrapperNode.clipsToBounds = true
|
|
case .info:
|
|
self.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)
|
|
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
|
|
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
|
|
self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
|
|
self.panelWrapperNode.addSubnode(self.titleNode)
|
|
self.panelWrapperNode.addSubnode(self.textNode)
|
|
self.panelWrapperNode.addSubnode(self.buttonNode)
|
|
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
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.fetchResourceDisposable?.dispose()
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
self.panelNode.view.addSubview(self.effectView)
|
|
}
|
|
|
|
@objc private func buttonPressed() {
|
|
if self.action(.info) {
|
|
self.dismiss()
|
|
}
|
|
}
|
|
|
|
@objc private func undoButtonPressed() {
|
|
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 {
|
|
if Int(self.remainingSeconds) != previousRemainingSeconds || (self.timerTextNode.attributedText?.string ?? "").isEmpty {
|
|
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()
|
|
})
|
|
}
|
|
self.timerTextNode.attributedText = NSAttributedString(string: "\(Int(self.remainingSeconds))", font: Font.regular(16.0), textColor: .white)
|
|
if let validLayout = self.validLayout {
|
|
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
|
}
|
|
}
|
|
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.checkTimer()
|
|
}
|
|
|
|
func updateContent(_ content: UndoOverlayContent) {
|
|
self.content = content
|
|
|
|
switch content {
|
|
case let .image(image, text):
|
|
self.iconNode?.image = image
|
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
|
default:
|
|
break
|
|
}
|
|
|
|
self.renewWithCurrentContent()
|
|
|
|
if let validLayout = self.validLayout {
|
|
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
let firstLayout = self.validLayout == nil
|
|
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 iconSize = preferredSize {
|
|
if iconSize.width > leftInset {
|
|
leftInset = iconSize.width - 8.0
|
|
}
|
|
}
|
|
let rightInset: CGFloat = 16.0
|
|
var contentHeight: CGFloat = 20.0
|
|
|
|
let margin: CGFloat = 12.0
|
|
|
|
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 - margin * 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 - margin, height: .greatestFiniteMagnitude))
|
|
let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
|
|
|
|
if !titleSize.width.isZero {
|
|
contentHeight += titleSize.height + 1.0
|
|
}
|
|
contentHeight += textSize.height
|
|
|
|
contentHeight = max(49.0, contentHeight)
|
|
|
|
var insets = layout.insets(options: [.input])
|
|
if self.elevatedLayout {
|
|
insets.bottom += 49.0
|
|
}
|
|
|
|
let panelFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
|
let panelWrapperFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
|
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 - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
|
|
|
|
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
|
|
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
|
|
|
|
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
|
|
self.undoButtonNode.frame = undoButtonFrame
|
|
|
|
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(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)
|
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize))
|
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize))
|
|
|
|
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(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 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))
|
|
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
|
|
transition.updateFrame(node: statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)))
|
|
if firstLayout {
|
|
statusNode.transitionToState(.secretTimeout(color: .white, icon: nil, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
|
|
}
|
|
}
|
|
|
|
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)))
|
|
}
|
|
}
|
|
|
|
func animateIn(asReplacement: Bool) {
|
|
if asReplacement {
|
|
let 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)
|
|
}
|
|
|
|
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
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
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 !self.panelNode.frame.insetBy(dx: -60.0, dy: 0.0).contains(point) {
|
|
return nil
|
|
}
|
|
return super.hitTest(point, with: event)
|
|
}
|
|
}
|