Swiftgram/TelegramUI/ChatItemGalleryFooterContentNode.swift
2019-01-22 21:58:59 +04:00

869 lines
46 KiB
Swift

import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
import Photos
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white)
private let backwardImage = UIImage(bundleImageName: "Media Gallery/BackwardButton")
private let forwardImage = UIImage(bundleImageName: "Media Gallery/ForwardButton")
private let pauseImage = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let color = UIColor.white
let diameter: CGFloat = 16.0
context.setFillColor(color.cgColor)
context.translateBy(x: (diameter - size.width) / 2.0, y: (diameter - size.height) / 2.0)
let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ")
context.fillPath()
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
context.translateBy(x: -(diameter - size.width) / 2.0, y: -(diameter - size.height) / 2.0)
})
private let playImage = generateImage(CGSize(width: 15.0, height: 18.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let color = UIColor.white
let diameter: CGFloat = 16.0
context.setFillColor(color.cgColor)
context.translateBy(x: (diameter - size.width) / 2.0 + 1.5, y: (diameter - size.height) / 2.0 + 1.0)
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 0.8, y: 0.8)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ")
context.fillPath()
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0)
})
private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white)
private let titleFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)
enum ChatItemGalleryFooterContent: Equatable {
case info
case fetch(status: MediaResourceStatus)
case playback(paused: Bool, seekable: Bool)
static func ==(lhs: ChatItemGalleryFooterContent, rhs: ChatItemGalleryFooterContent) -> Bool {
switch lhs {
case .info:
if case .info = rhs {
return true
} else {
return false
}
case let .fetch(lhsStatus):
if case let .fetch(rhsStatus) = rhs, lhsStatus == rhsStatus {
return true
} else {
return false
}
case let .playback(lhsPaused, lhsSeekable):
if case let .playback(rhsPaused, rhsSeekable) = rhs, lhsPaused == rhsPaused, lhsSeekable == rhsSeekable {
return true
} else {
return false
}
}
}
}
enum ChatItemGalleryFooterContentTapAction {
case none
case url(url: String, concealed: Bool)
case textMention(String)
case peerMention(PeerId, String)
case botCommand(String)
case hashtag(String?, String)
case instantPage
case call(PeerId)
case openMessage
case ignore
}
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
private let context: AccountContext
private var theme: PresentationTheme
private var strings: PresentationStrings
private var dateTimeFormat: PresentationDateTimeFormat
private let deleteButton: UIButton
private let actionButton: UIButton
private let textNode: ImmediateTextNode
private let authorNameNode: ASTextNode
private let dateNode: ASTextNode
private let backwardButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let playbackControlButton: HighlightableButtonNode
private let statusButtonNode: HighlightTrackingButtonNode
private let statusNode: RadialStatusNode
private var currentMessageText: NSAttributedString?
private var currentAuthorNameText: String?
private var currentDateText: String?
private var currentMessage: Message?
private var currentWebPageAndMedia: (TelegramMediaWebpage, Media)?
private let messageContextDisposable = MetaDisposable()
var playbackControl: (() -> Void)?
var seekBackward: (() -> Void)?
var seekForward: (() -> Void)?
var fetchControl: (() -> Void)?
var performAction: ((GalleryControllerInteractionTapAction) -> Void)?
var openActionOptions: ((GalleryControllerInteractionTapAction) -> Void)?
var content: ChatItemGalleryFooterContent = .info {
didSet {
if self.content != oldValue {
switch self.content {
case .info:
self.authorNameNode.isHidden = false
self.dateNode.isHidden = false
self.backwardButton.isHidden = true
self.forwardButton.isHidden = true
self.playbackControlButton.isHidden = true
self.statusButtonNode.isHidden = true
self.statusNode.isHidden = true
case let .fetch(status):
self.authorNameNode.isHidden = true
self.dateNode.isHidden = true
self.backwardButton.isHidden = true
self.forwardButton.isHidden = true
self.playbackControlButton.isHidden = true
self.statusButtonNode.isHidden = false
self.statusNode.isHidden = false
var statusState: RadialStatusNodeState = .none
switch status {
case let .Fetching(isActive, progress):
let adjustedProgress = max(progress, 0.027)
statusState = .cloudProgress(color: UIColor.white, strokeBackgroundColor: UIColor.white.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress))
case .Local:
break
case .Remote:
if let image = cloudFetchIcon {
statusState = .customIcon(image)
}
}
self.statusNode.transitionToState(statusState, completion: {})
self.statusButtonNode.isUserInteractionEnabled = statusState != .none
case let .playback(paused, seekable):
self.authorNameNode.isHidden = true
self.dateNode.isHidden = true
self.backwardButton.isHidden = !seekable
self.forwardButton.isHidden = !seekable
self.playbackControlButton.isHidden = false
self.playbackControlButton.setImage(paused ? playImage : pauseImage, for: [])
self.statusButtonNode.isHidden = true
self.statusNode.isHidden = true
}
}
}
}
var scrubberView: ChatVideoGalleryItemScrubberView? = nil {
willSet {
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
scrubberView.removeFromSuperview()
}
}
didSet {
if let scrubberView = self.scrubberView {
self.view.addSubview(scrubberView)
}
}
}
init(context: AccountContext, presentationData: PresentationData) {
self.context = context
self.theme = presentationData.theme
self.strings = presentationData.strings
self.dateTimeFormat = presentationData.dateTimeFormat
self.deleteButton = UIButton()
self.actionButton = UIButton()
self.deleteButton.setImage(deleteImage, for: [.normal])
self.actionButton.setImage(actionImage, for: [.normal])
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
self.authorNameNode = ASTextNode()
self.authorNameNode.maximumNumberOfLines = 1
self.authorNameNode.isUserInteractionEnabled = false
self.authorNameNode.displaysAsynchronously = false
self.dateNode = ASTextNode()
self.dateNode.maximumNumberOfLines = 1
self.dateNode.isUserInteractionEnabled = false
self.dateNode.displaysAsynchronously = false
self.backwardButton = HighlightableButtonNode()
self.backwardButton.isHidden = true
self.backwardButton.setImage(backwardImage, for: [])
self.forwardButton = HighlightableButtonNode()
self.forwardButton.isHidden = true
self.forwardButton.setImage(forwardImage, for: [])
self.playbackControlButton = HighlightableButtonNode()
self.playbackControlButton.isHidden = true
self.statusButtonNode = HighlightTrackingButtonNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
self.statusNode.isUserInteractionEnabled = false
super.init()
self.textNode.highlightAttributeAction = { attributes in
let highlightedAttributes = [TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag]
for attribute in highlightedAttributes {
if let _ = attributes[NSAttributedStringKey(rawValue: attribute)] {
return NSAttributedStringKey(rawValue: attribute)
}
}
return nil
}
self.textNode.tapAttributeAction = { [weak self] attributes in
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes) {
strongSelf.performAction?(action)
}
}
self.textNode.longTapAttributeAction = { [weak self] attributes in
if let strongSelf = self, let action = strongSelf.actionForAttributes(attributes) {
strongSelf.openActionOptions?(action)
}
}
self.view.addSubview(self.deleteButton)
self.view.addSubview(self.actionButton)
self.addSubnode(self.textNode)
self.addSubnode(self.authorNameNode)
self.addSubnode(self.dateNode)
self.addSubnode(self.backwardButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.playbackControlButton)
self.addSubnode(self.statusNode)
self.addSubnode(self.statusButtonNode)
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
self.backwardButton.addTarget(self, action: #selector(self.backwardButtonPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
self.playbackControlButton.addTarget(self, action: #selector(self.playbackControlPressed), forControlEvents: .touchUpInside)
self.statusButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.statusNode.layer.removeAnimation(forKey: "opacity")
strongSelf.statusNode.alpha = 0.4
} else {
strongSelf.statusNode.alpha = 1.0
strongSelf.statusNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.statusButtonNode.addTarget(self, action: #selector(self.statusPressed), forControlEvents: .touchUpInside)
}
deinit {
self.messageContextDisposable.dispose()
}
private func actionForAttributes(_ attributes: [NSAttributedStringKey: Any]) -> GalleryControllerInteractionTapAction? {
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
// var concealed = true
// if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
// concealed = !doesUrlMatchText(url: url, text: attributeText)
// }
return .url(url: url, concealed: false)
} else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
} else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand)] as? String {
return .botCommand(botCommand)
} else if let hashtag = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
return .hashtag(hashtag.peerName, hashtag.hashtag)
} else {
return nil
}
}
func setup(origin: GalleryItemOriginData?, caption: NSAttributedString) {
let titleText = origin?.title
let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0) }
if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText {
self.currentMessageText = caption
self.currentAuthorNameText = titleText
self.currentDateText = dateText
if caption.length == 0 {
self.textNode.isHidden = true
self.textNode.attributedText = nil
} else {
self.textNode.isHidden = false
self.textNode.attributedText = caption
}
if let titleText = titleText {
self.authorNameNode.attributedText = NSAttributedString(string: titleText, font: titleFont, textColor: .white)
} else {
self.authorNameNode.attributedText = nil
}
if let dateText = dateText {
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
} else {
self.dateNode.attributedText = nil
}
self.requestLayout?(.immediate)
}
self.deleteButton.isHidden = origin == nil
}
func setMessage(_ message: Message) {
self.currentMessage = message
self.actionButton.isHidden = message.containsSecretMedia
let canDelete: Bool
if let peer = message.peers[message.id.peerId] {
if peer is TelegramUser || peer is TelegramSecretChat {
canDelete = true
} else if let _ = peer as? TelegramGroup {
canDelete = true
} else if let channel = peer as? TelegramChannel {
if message.flags.contains(.Incoming) {
canDelete = channel.hasPermission(.deleteAllMessages)
} else {
canDelete = true
}
} else {
canDelete = false
}
} else {
canDelete = false
}
var authorNameText: String?
if let author = message.author {
authorNameText = author.displayTitle
} else if let peer = message.peers[message.id.peerId] {
authorNameText = peer.displayTitle
}
let dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: message.timestamp)
var messageText = NSAttributedString(string: "")
var hasCaption = false
for media in message.media {
if media is TelegramMediaImage {
hasCaption = true
} else if let file = media as? TelegramMediaFile {
hasCaption = file.mimeType.hasPrefix("image/")
}
}
if hasCaption {
var entities: [MessageTextEntity] = []
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
entities = attribute.entities
break
}
}
messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities)
}
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
self.currentMessageText = messageText
if messageText.length == 0 {
self.textNode.isHidden = true
self.textNode.attributedText = nil
} else {
self.textNode.isHidden = false
self.textNode.attributedText = messageText
}
if let authorNameText = authorNameText {
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
} else {
self.authorNameNode.attributedText = nil
}
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
self.deleteButton.isHidden = !canDelete
self.requestLayout?(.immediate)
}
}
func setWebPage(_ webPage: TelegramMediaWebpage, media: Media) {
self.currentWebPageAndMedia = (webPage, media)
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
var panelHeight: CGFloat = 44.0 + bottomInset
panelHeight += contentInset
var textFrame = CGRect()
if !self.textNode.isHidden {
let sideInset: CGFloat = 8.0 + leftInset
let topInset: CGFloat = 8.0
let textBottomInset: CGFloat = 8.0
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
panelHeight += textSize.height + topInset + textBottomInset
textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize)
}
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
let sideInset: CGFloat = 8.0 + leftInset
let topInset: CGFloat = 8.0
let bottomInset: CGFloat = 2.0
panelHeight += 34.0 + topInset + bottomInset
if self.textNode.isHidden {
panelHeight += 8.0
}
scrubberView.frame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textFrame.maxY), size: CGSize(width: width - sideInset * 2.0, height: 34.0))
}
self.textNode.frame = textFrame
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
let statusSize = CGSize(width: 28.0, height: 28.0)
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floor((width - statusSize.width) / 2.0), y: panelHeight - bottomInset - statusSize.height - 8.0), size: statusSize))
self.statusButtonNode.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
let authorNameSize = self.authorNameNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude))
let dateSize = self.dateNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
if authorNameSize.height.isZero {
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize)
} else {
let labelsSpacing: CGFloat = 0.0
self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((width - authorNameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize)
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
}
return panelHeight
}
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
if let previousContentNode = previousContentNode as? ChatItemGalleryFooterContentNode, previousContentNode.scrubberView != nil {
} else {
transition.animatePositionAdditive(layer: scrubberView.layer, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
}
scrubberView.alpha = 1.0
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
self.textNode.alpha = 1.0
self.dateNode.alpha = 1.0
self.authorNameNode.alpha = 1.0
self.deleteButton.alpha = 1.0
self.actionButton.alpha = 1.0
self.backwardButton.alpha = 1.0
self.forwardButton.alpha = 1.0
self.statusNode.alpha = 1.0
self.playbackControlButton.alpha = 1.0
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
if let nextContentNode = nextContentNode as? ChatItemGalleryFooterContentNode, nextContentNode.scrubberView != nil {
} else {
transition.updateFrame(view: scrubberView, frame: scrubberView.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
}
scrubberView.alpha = 0.0
scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
}
transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
self.textNode.alpha = 0.0
self.dateNode.alpha = 0.0
self.authorNameNode.alpha = 0.0
self.deleteButton.alpha = 0.0
self.actionButton.alpha = 0.0
self.backwardButton.alpha = 0.0
self.forwardButton.alpha = 0.0
self.statusNode.alpha = 0.0
self.playbackControlButton.alpha = 0.0
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in
completion()
})
}
@objc func deleteButtonPressed() {
if let currentMessage = self.currentMessage {
let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
return transaction.getMessageGroup(currentMessage.id) ?? []
} |> deliverOnMainQueue).start(next: { [weak self] messages in
if let strongSelf = self, !messages.isEmpty {
if messages.count == 1 {
strongSelf.commitDeleteMessages(messages, ask: true)
} else {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
var generalMessageContentKind: MessageContentKind?
for message in messages {
let currentKind = messageContentKind(message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.context.account.peerId)
if generalMessageContentKind == nil || generalMessageContentKind == currentKind {
generalMessageContentKind = currentKind
} else {
generalMessageContentKind = nil
break
}
}
var singleText = presentationData.strings.Media_ShareItem(1)
var multipleText = presentationData.strings.Media_ShareItem(Int32(messages.count))
if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind {
case .image:
singleText = presentationData.strings.Media_ShareThisPhoto
multipleText = presentationData.strings.Media_SharePhoto(Int32(messages.count))
case .video:
singleText = presentationData.strings.Media_ShareThisVideo
multipleText = presentationData.strings.Media_ShareVideo(Int32(messages.count))
default:
break
}
}
let deleteAction: ([Message]) -> Void = { messages in
if let strongSelf = self {
strongSelf.commitDeleteMessages(messages, ask: false)
}
}
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: singleText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
deleteAction([currentMessage])
}),
ActionSheetButtonItem(title: multipleText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
deleteAction(messages)
})
]
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
strongSelf.controllerInteraction?.presentController(actionSheet, nil)
}
}
})
}
}
private func commitDeleteMessages(_ messages: [Message], ask: Bool) {
self.messageContextDisposable.set((chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: Set(messages.map { $0.id })) |> deliverOnMainQueue).start(next: { [weak self] actions in
if let strongSelf = self, let controllerInteration = strongSelf.controllerInteraction, !actions.options.isEmpty {
let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme)
var items: [ActionSheetItem] = []
var personalPeerName: String?
var isChannel = false
let peerId: PeerId = messages[0].id.peerId
if let user = messages[0].peers[messages[0].id.peerId] as? TelegramUser {
personalPeerName = user.compactDisplayTitle
} else if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info {
isChannel = true
}
if actions.options.contains(.deleteGlobally) {
let globalTitle: String
if isChannel {
globalTitle = strongSelf.strings.Common_Delete
} else if let personalPeerName = personalPeerName {
globalTitle = strongSelf.strings.Conversation_DeleteMessagesFor(personalPeerName).0
} else {
globalTitle = strongSelf.strings.Conversation_DeleteMessagesForEveryone
}
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start()
strongSelf.controllerInteraction?.dismissController()
}
}))
}
if actions.options.contains(.deleteLocally) {
var localOptionText = strongSelf.strings.Conversation_DeleteMessagesForMe
if strongSelf.context.account.peerId == peerId {
localOptionText = strongSelf.strings.Conversation_Moderate_Delete
}
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forLocalPeer).start()
strongSelf.controllerInteraction?.dismissController()
}
}))
}
if !ask && items.count == 1 {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start()
strongSelf.controllerInteraction?.dismissController()
} else {
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
controllerInteration.presentController(actionSheet, nil)
}
}
}))
}
@objc func actionButtonPressed() {
if let currentMessage = self.currentMessage {
let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
return transaction.getMessageGroup(currentMessage.id) ?? []
} |> deliverOnMainQueue).start(next: { [weak self] messages in
if let strongSelf = self, !messages.isEmpty {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
var generalMessageContentKind: MessageContentKind?
for message in messages {
let currentKind = messageContentKind(message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.context.account.peerId)
if generalMessageContentKind == nil || generalMessageContentKind == currentKind {
generalMessageContentKind = currentKind
} else {
generalMessageContentKind = nil
break
}
}
var preferredAction = ShareControllerPreferredAction.default
if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind {
case .image, .video:
preferredAction = .saveToCameraRoll
default:
break
}
}
if messages.count == 1 {
var subject: ShareControllerSubject = ShareControllerSubject.messages(messages)
for m in messages[0].media {
if let image = m as? TelegramMediaImage {
subject = .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(messages[0]), media: m), resource: $0.resource)) }))
} else if let webpage = m as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if content.embedType == "iframe" {
let item = OpenInItem.url(url: content.url)
if availableOpenInOptions(context: strongSelf.context, item: item).count > 1 {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in
if let strongSelf = self {
let openInController = OpenInActionSheetController(context: strongSelf.context, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self {
openExternalUrl(context: strongSelf.context, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
})
strongSelf.controllerInteraction?.presentController(openInController, nil)
}
}))
} else {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Web_OpenExternal, action: { [weak self] in
if let strongSelf = self {
openExternalUrl(context: strongSelf.context, url: content.url, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
}))
}
} else {
if let file = content.file {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: file))
preferredAction = .saveToCameraRoll
} else if let image = content.image {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: image))
preferredAction = .saveToCameraRoll
}
}
} else if let file = m as? TelegramMediaFile {
subject = .media(.message(message: MessageReference(messages[0]), media: file))
if file.isAnimated {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in
if let strongSelf = self {
let message = messages[0]
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file)).start()
}
}))
} else if file.mimeType.hasPrefix("image/") || file.mimeType.hasPrefix("video/") {
preferredAction = .saveToCameraRoll
}
}
}
let shareController = ShareController(context: strongSelf.context, subject: subject, preferredAction: preferredAction)
strongSelf.controllerInteraction?.presentController(shareController, nil)
} else {
var singleText = presentationData.strings.Media_ShareItem(1)
var multipleText = presentationData.strings.Media_ShareItem(Int32(messages.count))
if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind {
case .image:
singleText = presentationData.strings.Media_ShareThisPhoto
multipleText = presentationData.strings.Media_SharePhoto(Int32(messages.count))
case .video:
singleText = presentationData.strings.Media_ShareThisVideo
multipleText = presentationData.strings.Media_ShareVideo(Int32(messages.count))
default:
break
}
}
let shareAction: ([Message]) -> Void = { messages in
if let strongSelf = self {
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages), preferredAction: preferredAction)
strongSelf.controllerInteraction?.presentController(shareController, nil)
}
}
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: singleText, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
shareAction([currentMessage])
}),
ActionSheetButtonItem(title: multipleText, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
shareAction(messages)
})
]
actionSheet.setItemGroups([ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
strongSelf.controllerInteraction?.presentController(actionSheet, nil)
}
}
})
} else if let (webPage, media) = self.currentWebPageAndMedia {
let presentationData = self.context.currentPresentationData.with { $0 }
var preferredAction = ShareControllerPreferredAction.default
var subject = ShareControllerSubject.media(.webPage(webPage: WebpageReference(webPage), media: media))
if let file = media as? TelegramMediaFile {
if file.isAnimated {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in
if let strongSelf = self {
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: .webPage(webPage: WebpageReference(webPage), media: file)).start()
}
}))
} else if file.mimeType.hasPrefix("image/") || file.mimeType.hasPrefix("video/") {
preferredAction = .saveToCameraRoll
}
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if content.embedType == "iframe" || content.embedType == "video" {
subject = .url(content.url)
let item = OpenInItem.url(url: content.url)
if availableOpenInOptions(context: self.context, item: item).count > 1 {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in
if let strongSelf = self {
let openInController = OpenInActionSheetController(context: strongSelf.context, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self {
openExternalUrl(context: strongSelf.context, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
})
strongSelf.controllerInteraction?.presentController(openInController, nil)
}
}))
} else {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Web_OpenExternal, action: { [weak self] in
if let strongSelf = self {
openExternalUrl(context: strongSelf.context, url: content.url, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
}))
}
} else {
if let file = content.file {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: file))
preferredAction = .saveToCameraRoll
} else if let image = content.image {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: image))
preferredAction = .saveToCameraRoll
}
}
}
let shareController = ShareController(context: self.context, subject: subject, preferredAction: preferredAction)
self.controllerInteraction?.presentController(shareController, nil)
}
}
@objc func playbackControlPressed() {
self.playbackControl?()
}
@objc func backwardButtonPressed() {
self.seekBackward?()
}
@objc func forwardButtonPressed() {
self.seekForward?()
}
@objc private func statusPressed() {
self.fetchControl?()
}
}