This commit is contained in:
Ali
2021-02-02 18:48:26 +04:00
parent c4b41cbe66
commit e80c198605
30 changed files with 4727 additions and 4267 deletions

View File

@@ -3,6 +3,7 @@ import UIKit
import Postbox
import TelegramCore
import SyncCore
import AsyncDisplayKit
import Display
import UIKit
import SwiftSignalKit
@@ -15,6 +16,8 @@ import LegacyUI
import AppBundle
import SaveToCameraRoll
import PresentationDataUtils
import TelegramPresentationData
import TelegramStringFormatting
private struct MessageContextMenuData {
let starStatus: Bool?
@@ -881,6 +884,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
if !isReplyThreadHead, (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction {
var autoremoveDeadline: Int32?
for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
if let countdownBeginTime = attribute.countdownBeginTime {
autoremoveDeadline = countdownBeginTime + attribute.timeout
}
break
}
}
let title: String
var isSending = false
var isEditing = false
@@ -894,16 +907,27 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} else {
title = chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete
}
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
})))
if let autoremoveDeadline = autoremoveDeadline, !isEditing, !isSending {
actions.append(.custom(ChatDeleteMessageContextItem(timestamp: Double(autoremoveDeadline), action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
}), false))
} else {
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
})))
}
}
if !isPinnedMessages, !isReplyThreadHead, data.canSelect {
@@ -1195,3 +1219,228 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me
}
}
}
final class ChatDeleteMessageContextItem: ContextMenuCustomItem {
fileprivate let timestamp: Double
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
init(timestamp: Double, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.timestamp = timestamp
self.action = action
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return ChatDeleteMessageContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
private let textFont = Font.regular(17.0)
private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: ChatDeleteMessageContextItem
private let presentationData: PresentationData
private let getController: () -> ContextController?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let statusNode: ImmediateTextNode
private let iconNode: ASImageNode
private let textIconNode: ASImageNode
private let buttonNode: HighlightTrackingButtonNode
private var timer: SwiftSignalKit.Timer?
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, item: ChatDeleteMessageContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController
self.actionSelected = actionSelected
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.isAccessibilityElement = false
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuDelete, font: textFont, textColor: presentationData.theme.contextMenu.destructiveColor)
self.textNode.maximumNumberOfLines = 1
let statusNode = ImmediateTextNode()
statusNode.isAccessibilityElement = false
statusNode.isUserInteractionEnabled = false
statusNode.displaysAsynchronously = false
statusNode.attributedText = NSAttributedString(string: stringForRemainingTime(Int32(max(0.0, self.item.timestamp - Date().timeIntervalSince1970)), strings: presentationData.strings), font: subtextFont, textColor: presentationData.theme.contextMenu.destructiveColor)
statusNode.maximumNumberOfLines = 1
self.statusNode = statusNode
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
self.iconNode = ASImageNode()
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: presentationData.theme.actionSheet.destructiveActionTextColor)
self.textIconNode = ASImageNode()
self.textIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SelfExpiring"), color: presentationData.theme.actionSheet.destructiveActionTextColor)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.statusNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textIconNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.timer?.invalidate()
}
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
})
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.updateTime(transition: .immediate)
}, queue: Queue.mainQueue())
self.timer = timer
timer.start()
}
private var validLayout: CGSize?
func updateTime(transition: ContainedViewLayoutTransition) {
guard let size = self.validLayout else {
return
}
let subtextFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.statusNode.attributedText = NSAttributedString(string: stringForRemainingTime(Int32(max(0.0, self.item.timestamp - Date().timeIntervalSince1970)), strings: presentationData.strings), font: subtextFont, textColor: presentationData.theme.contextMenu.destructiveColor)
let sideInset: CGFloat = 16.0
let statusSize = self.statusNode.updateLayout(CGSize(width: size.width - sideInset - 32.0, height: .greatestFiniteMagnitude))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: self.statusNode.frame.minX, y: self.statusNode.frame.minY), size: statusSize))
}
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let iconSideInset: CGFloat = 12.0
let verticalInset: CGFloat = 12.0
let iconSize: CGSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
let textIconSize: CGSize = self.textIconNode.image?.size ?? CGSize(width: 2.0, height: 2.0)
let standardIconWidth: CGFloat = 32.0
var rightTextInset: CGFloat = sideInset
if !iconSize.width.isZero {
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset
}
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
let statusSize = self.statusNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset - textIconSize.width - 2.0, height: .greatestFiniteMagnitude))
let verticalSpacing: CGFloat = 2.0
let combinedTextHeight = textSize.height + verticalSpacing + statusSize.height
return (CGSize(width: max(textSize.width, statusSize.width) + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
self.validLayout = size
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.textIconNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height + floorToScreenPixels((statusSize.height - textIconSize.height) / 2.0) + 1.0), size: textIconSize))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: sideInset + textIconSize.width + 2.0, y: verticalOrigin + verticalSpacing + textSize.height), size: statusSize))
if !iconSize.width.isZero {
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
func updateTheme(presentationData: PresentationData) {
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
self.statusNode.attributedText = NSAttributedString(string: self.statusNode.attributedText?.string ?? "", font: subtextFont, textColor: presentationData.theme.contextMenu.secondaryColor)
}
@objc private func buttonPressed() {
self.performAction()
}
func performAction() {
guard let controller = self.getController() else {
return
}
self.item.action(controller, { [weak self] result in
self?.actionSelected(result)
})
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
}
private func stringForRemainingTime(_ duration: Int32, strings: PresentationStrings) -> String {
let days = duration / (3600 * 24)
let hours = duration / 3600
let minutes = duration / 60 % 60
let seconds = duration % 60
let durationString: String
if days > 0 {
return strings.Conversation_AutoremoveRemainingDays(days)
} else if hours > 0 {
durationString = String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
durationString = String(format: "%d:%02d", minutes, seconds)
}
return strings.Conversation_AutoremoveRemainingTime(durationString).0
}