Swiftgram/TelegramUI/EditAccessoryPanelNode.swift
2018-07-21 21:31:48 +03:00

332 lines
15 KiB
Swift

import Foundation
import AsyncDisplayKit
import TelegramCore
import Postbox
import SwiftSignalKit
import Display
final class EditAccessoryPanelNode: AccessoryPanelNode {
let messageId: MessageId
let closeButton: ASButtonNode
let lineNode: ASImageNode
let titleNode: ASTextNode
let textNode: ASTextNode
let imageNode: TransformImageNode
private let activityIndicator: ActivityIndicator
private let statusNode: RadialStatusNode
private let tapNode: ASDisplayNode
private let messageDisposable = MetaDisposable()
private let editingMessageDisposable = MetaDisposable()
private var currentMessage: Message?
private var currentEditMediaReference: AnyMediaReference?
private var previousMediaReference: AnyMediaReference?
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
didSet {
if let statuses = self.interfaceInteraction?.statuses {
self.editingMessageDisposable.set(statuses.editingMessage.start(next: { [weak self] value in
if let strongSelf = self {
if let value = value {
if value.isZero {
strongSelf.activityIndicator.isHidden = false
strongSelf.statusNode.transitionToState(.none, completion: {})
} else {
strongSelf.activityIndicator.isHidden = true
strongSelf.statusNode.transitionToState(.progress(color: strongSelf.theme.chat.inputPanel.panelControlAccentColor, value: CGFloat(value), cancelEnabled: false), completion: {})
}
} else {
strongSelf.activityIndicator.isHidden = true
strongSelf.statusNode.transitionToState(.none, completion: {})
}
}
}))
}
}
}
private let account: Account
var theme: PresentationTheme
var strings: PresentationStrings
init(account: Account, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings) {
self.account = account
self.messageId = messageId
self.theme = theme
self.strings = strings
self.closeButton = ASButtonNode()
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.closeButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0)
self.closeButton.displaysAsynchronously = false
self.lineNode = ASImageNode()
self.lineNode.displayWithoutProcessing = true
self.lineNode.displaysAsynchronously = false
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
self.titleNode = ASTextNode()
self.titleNode.truncationMode = .byTruncatingTail
self.titleNode.maximumNumberOfLines = 1
self.titleNode.displaysAsynchronously = false
self.textNode = ASTextNode()
self.textNode.truncationMode = .byTruncatingTail
self.textNode.maximumNumberOfLines = 1
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = true
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = [.subsequentUpdates]
self.imageNode.isHidden = true
self.imageNode.isUserInteractionEnabled = true
self.activityIndicator = ActivityIndicator(type: .custom(theme.chat.inputPanel.panelControlAccentColor, 22.0, 2.0))
self.activityIndicator.isHidden = true
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
self.tapNode = ASDisplayNode()
super.init()
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
self.addSubnode(self.closeButton)
self.addSubnode(self.lineNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.imageNode)
self.addSubnode(self.activityIndicator)
self.addSubnode(self.statusNode)
self.addSubnode(self.tapNode)
self.messageDisposable.set((account.postbox.messageAtId(messageId)
|> deliverOnMainQueue).start(next: { [weak self] message in
self?.updateMessage(message)
}))
}
deinit {
self.messageDisposable.dispose()
self.editingMessageDisposable.dispose()
}
override func didLoad() {
super.didLoad()
self.tapNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.contentTap(_:))))
}
private func updateMessage(_ message: Message?) {
self.currentMessage = message
var text = ""
if let message = message {
var effectiveMessage = message
if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}
(text, _) = descriptionStringForMessage(effectiveMessage, strings: self.strings, accountPeerId: self.account.peerId)
}
var updatedMediaReference: AnyMediaReference?
var imageDimensions: CGSize?
if let message = message, !message.containsSecretMedia {
var candidateMediaReference: AnyMediaReference?
if let currentEditMedia = self.currentEditMediaReference {
candidateMediaReference = currentEditMedia
} else {
for media in message.media {
if media is TelegramMediaImage || media is TelegramMediaFile {
candidateMediaReference = .message(message: MessageReference(message), media: media)
break
}
}
}
if let imageReference = candidateMediaReference?.concrete(TelegramMediaImage.self) {
updatedMediaReference = imageReference.abstract
if let representation = largestRepresentationForPhoto(imageReference.media) {
imageDimensions = representation.dimensions
}
} else if let fileReference = candidateMediaReference?.concrete(TelegramMediaFile.self) {
updatedMediaReference = fileReference.abstract
if !fileReference.media.isInstantVideo, let representation = largestImageRepresentation(fileReference.media.previewRepresentations), !fileReference.media.isSticker {
imageDimensions = representation.dimensions
}
}
}
let imageNodeLayout = self.imageNode.asyncLayout()
var applyImage: (() -> Void)?
if let imageDimensions = imageDimensions {
let boundingSize = CGSize(width: 35.0, height: 35.0)
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
}
var mediaUpdated = false
if let updatedMediaReference = updatedMediaReference, let previousMediaReference = self.previousMediaReference {
mediaUpdated = !updatedMediaReference.media.isEqual(previousMediaReference.media)
} else if (updatedMediaReference != nil) != (self.previousMediaReference != nil) {
mediaUpdated = true
}
self.previousMediaReference = updatedMediaReference
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if mediaUpdated {
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
updateImageSignal = chatMessagePhotoThumbnail(account: self.account, photoReference: imageReference)
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
if fileReference.media.isVideo {
updateImageSignal = chatMessageVideoThumbnail(account: self.account, fileReference: fileReference)
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
updateImageSignal = chatWebpageSnippetFile(account: account, fileReference: fileReference, representation: iconImageRepresentation)
}
}
} else {
updateImageSignal = .single({ _ in return nil })
}
}
let isMedia: Bool
if let message = message {
var effectiveMessage = message
if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
}
switch messageContentKind(effectiveMessage, strings: strings, accountPeerId: self.account.peerId) {
case .text:
isMedia = false
default:
isMedia = true
}
} else {
isMedia = false
}
let canEditMedia: Bool
if let message = message, !messageMediaEditingOptions(message: message).isEmpty {
canEditMedia = true
} else {
canEditMedia = false
}
self.titleNode.attributedText = NSAttributedString(string: canEditMedia ? self.strings.Conversation_EditingCaptionPanelTitle : self.strings.Conversation_EditingMessagePanelTitle, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: isMedia ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
if let applyImage = applyImage {
applyImage()
self.imageNode.isHidden = false
} else {
self.imageNode.isHidden = true
}
if let updateImageSignal = updateImageSignal {
self.imageNode.setSignal(.single({ arguments in
/*let context = DrawingContext(size: arguments.boundingSize)
context.withContext { c in
c.setFillColor(UIColor.white.cgColor)
c.fill(CGRect(origin: CGPoint(), size: context.size))
}
return context*/
return nil
}) |> then(updateImageSignal))
}
self.setNeedsLayout()
}
override func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
if self.theme !== theme {
self.theme = theme
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
if let text = self.titleNode.attributedText?.string {
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
}
if let text = self.textNode.attributedText?.string {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor)
}
self.setNeedsLayout()
}
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 45.0)
}
override func updateState(size: CGSize, interfaceState: ChatPresentationInterfaceState) {
let editMedia = interfaceState.editMessageState?.media
var updatedEditMedia = false
if let currentEditMediaReference = self.currentEditMediaReference, let editMedia = editMedia {
if !currentEditMediaReference.media.isEqual(editMedia) {
updatedEditMedia = true
}
} else if (editMedia != nil) != (self.currentEditMediaReference != nil) {
updatedEditMedia = true
}
if updatedEditMedia {
if let editMedia = editMedia {
self.currentEditMediaReference = .standalone(media: editMedia)
} else {
self.currentEditMediaReference = nil
}
self.updateMessage(self.currentMessage)
}
}
override func layout() {
super.layout()
let bounds = self.bounds
let leftInset: CGFloat = 55.0
let textLineInset: CGFloat = 10.0
let rightInset: CGFloat = 55.0
let textRightInset: CGFloat = 20.0
let indicatorSize = CGSize(width: 22.0, height: 22.0)
self.activityIndicator.frame = CGRect(origin: CGPoint(x: 18.0, y: 15.0), size: indicatorSize)
self.statusNode.frame = CGRect(origin: CGPoint(x: 18.0, y: 15.0), size: indicatorSize).insetBy(dx: -2.0, dy: -2.0)
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
self.closeButton.frame = CGRect(origin: CGPoint(x: bounds.size.width - rightInset - closeButtonSize.width, y: 19.0), size: closeButtonSize)
self.lineNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 10.0))
var imageTextInset: CGFloat = 0.0
if !self.imageNode.isHidden {
imageTextInset = 9.0 + 35.0
}
self.imageNode.frame = CGRect(origin: CGPoint(x: leftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0))
let titleSize = self.titleNode.measure(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height))
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 7.0), size: titleSize)
let textSize = self.textNode.measure(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height))
self.textNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 25.0), size: textSize)
self.tapNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: bounds.width - leftInset - rightInset - closeButtonSize.width - 4.0, height: bounds.height))
}
@objc func closePressed() {
if let dismiss = self.dismiss {
dismiss()
}
}
@objc func contentTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state, let message = self.currentMessage {
self.interfaceInteraction?.navigateToMessage(message.id)
}
}
}