Display all reactions in context menu

This commit is contained in:
Peter 2019-08-23 15:35:07 +04:00
parent 47c4ab17a3
commit 5754dfb307
16 changed files with 332 additions and 155 deletions

View File

@ -408,32 +408,50 @@ final class MessageHistoryTable: Table {
return globallyUniqueIdToMessageId
}
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
for (peerId, messageIds) in self.messageIdsByPeerId(messageIds) {
var operations: [MessageHistoryIndexOperation] = []
for id in messageIds {
self.messageHistoryIndexTable.removeMessage(id, operations: &operations)
}
for operation in operations {
if case let .Remove(index) = operation {
if let message = self.getMessage(index) {
for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) {
forEachMedia(media)
}
}
}
}
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
}
}
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
var operations: [MessageHistoryIndexOperation] = []
self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations)
for operation in operations {
if case let .Remove(index) = operation {
if let message = self.getMessage(index) {
for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) {
forEachMedia(media)
}
}
}
}
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
}
func clearHistory(peerId: PeerId, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func clearHistory(peerId: PeerId, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
let indices = self.allMessageIndices(peerId: peerId)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
}
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
let indices = self.allIndicesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
}
func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {

View File

@ -72,28 +72,28 @@ public final class Transaction {
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
}
public func deleteMessages(_ messageIds: [MessageId]) {
public func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) {
assert(!self.disposed)
self.postbox?.deleteMessages(messageIds)
self.postbox?.deleteMessages(messageIds, forEachMedia: forEachMedia)
}
public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) {
public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) {
assert(!self.disposed)
self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId)
self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, forEachMedia: forEachMedia)
}
public func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace? = nil, _ f: (Message) -> Bool) {
self.postbox?.withAllMessages(peerId: peerId, namespace: namespace, f)
}
public func clearHistory(_ peerId: PeerId) {
public func clearHistory(_ peerId: PeerId, forEachMedia: (Media) -> Void) {
assert(!self.disposed)
self.postbox?.clearHistory(peerId)
self.postbox?.clearHistory(peerId, forEachMedia: forEachMedia)
}
public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
assert(!self.disposed)
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace)
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia)
}
public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] {
@ -105,11 +105,11 @@ public final class Transaction {
}
}
public func deleteMessagesWithGlobalIds(_ ids: [Int32]) {
public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: (Media) -> Void) {
assert(!self.disposed)
if let postbox = self.postbox {
let messageIds = postbox.messageIdsForGlobalIds(ids)
postbox.deleteMessages(messageIds)
postbox.deleteMessages(messageIds, forEachMedia: forEachMedia)
}
}
@ -1499,12 +1499,12 @@ public final class Postbox {
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
}
fileprivate func deleteMessages(_ messageIds: [MessageId]) {
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
}
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) {
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
}
fileprivate func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace?, _ f: (Message) -> Bool) {
@ -1519,15 +1519,15 @@ public final class Postbox {
}
}
fileprivate func clearHistory(_ peerId: PeerId) {
self.messageHistoryTable.clearHistory(peerId: peerId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
fileprivate func clearHistory(_ peerId: PeerId, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.clearHistory(peerId: peerId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) {
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations)
}
}
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
}
fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {

View File

@ -86,7 +86,9 @@ public final class ReactionContextNode: ASDisplayNode {
private let smallCircleNode: ASImageNode
private let smallCircleShadowNode: ASImageNode
private let contentContainer: ASDisplayNode
private var itemNodes: [ReactionNode] = []
private let disclosureButton: HighlightTrackingButtonNode
private var isExpanded: Bool = false
private var highlightedReaction: String?
@ -139,6 +141,28 @@ public final class ReactionContextNode: ASDisplayNode {
self.largeCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: largeCircleSize, shadowBlur: shadowBlur)
self.smallCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: smallCircleSize, shadowBlur: shadowBlur)
self.contentContainer = ASDisplayNode()
self.contentContainer.clipsToBounds = true
self.disclosureButton = HighlightTrackingButtonNode()
self.disclosureButton.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -6.0)
let buttonImage = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.contextMenu.dimColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(UIColor.clear.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setLineJoin(.round)
context.beginPath()
context.move(to: CGPoint(x: 8.0, y: size.height / 2.0 + 3.0))
context.addLine(to: CGPoint(x: size.width / 2.0, y: 11.0))
context.addLine(to: CGPoint(x: size.width - 8.0, y: size.height / 2.0 + 3.0))
context.strokePath()
})
self.disclosureButton.setImage(buttonImage, for: [])
super.init()
self.addSubnode(self.smallCircleShadowNode)
@ -150,10 +174,23 @@ public final class ReactionContextNode: ASDisplayNode {
self.backgroundContainerNode.addSubnode(self.backgroundNode)
self.addSubnode(self.backgroundContainerNode)
self.contentContainer.addSubnode(self.disclosureButton)
self.itemNodes = self.items.map { item in
return ReactionNode(account: account, theme: theme, reaction: .reaction(value: item.value, text: item.text, path: item.path), maximizedReactionSize: 30.0 - 18.0, loadFirstFrame: true)
}
self.itemNodes.forEach(self.addSubnode)
self.itemNodes.forEach(self.contentContainer.addSubnode)
self.addSubnode(self.contentContainer)
self.disclosureButton.addTarget(self, action: #selector(self.disclosurePressed), forControlEvents: .touchUpInside)
self.disclosureButton.highligthedChanged = { [weak self] highlighted in
if highlighted {
self?.disclosureButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.15, removeOnCompletion: false)
} else {
self?.disclosureButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.25)
}
}
}
override public func didLoad() {
@ -193,18 +230,27 @@ public final class ReactionContextNode: ASDisplayNode {
let minimizedItemSize: CGFloat = 30.0
let maximizedItemSize: CGFloat = 30.0 - 18.0
let shadowBlur: CGFloat = 5.0
let rowHeight: CGFloat = 52.0
let verticalInset: CGFloat = 11.0
let rowHeight: CGFloat = 30.0
let rowSpacing: CGFloat = itemSpacing
let columnCount = min(7, self.items.count)
let columnCount = min(6, self.items.count)
let contentWidth = CGFloat(columnCount) * minimizedItemSize + (CGFloat(columnCount) - 1.0) * itemSpacing + sideInset * 2.0
let rowCount = self.items.count / columnCount + (self.items.count % columnCount == 0 ? 0 : 1)
let contentHeight = rowHeight * CGFloat(rowCount)
let expandedRowCount = self.isExpanded ? rowCount : 1
let contentHeight = verticalInset * 2.0 + rowHeight * CGFloat(expandedRowCount) + CGFloat(expandedRowCount - 1) * rowSpacing
let (backgroundFrame, isLeftAligned) = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: anchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight))
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame)
for i in 0 ..< self.items.count {
let row = CGFloat(i / columnCount)
let column = CGFloat(i % columnCount)
let rowIndex = i / columnCount
let columnIndex = i % columnCount
let row = CGFloat(rowIndex)
let column = CGFloat(columnIndex)
var reactionValue: String?
switch self.itemNodes[i].reaction {
@ -224,18 +270,43 @@ public final class ReactionContextNode: ASDisplayNode {
itemSize = updatedSize
}
transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: backgroundFrame.minY + row * rowHeight + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize)), beginWithCurrentState: true)
let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: verticalInset + row * (rowHeight + rowSpacing) + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize))
transition.updateFrame(node: self.itemNodes[i], frame: itemFrame, beginWithCurrentState: true)
self.itemNodes[i].updateLayout(size: CGSize(width: itemSize, height: itemSize), scale: itemSize / (maximizedItemSize + 18.0), transition: transition, displayText: false)
self.itemNodes[i].updateIsAnimating(false, animated: false)
if row != 0 {
if rowIndex != 0 || columnIndex == columnCount - 1 {
if self.isExpanded {
self.itemNodes[i].alpha = 1.0
if self.itemNodes[i].alpha.isZero {
self.itemNodes[i].alpha = 1.0
if transition.isAnimated {
let delayOffset: Double = 1.0 - Double(columnIndex) / Double(columnCount - 1)
self.itemNodes[i].layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4 + delayOffset * 0.32, initialVelocity: 0.0, damping: 95.0)
self.itemNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05)
}
}
} else {
self.itemNodes[i].alpha = 0.0
}
} else {
self.itemNodes[i].alpha = 1.0
}
if rowIndex == 0 && columnIndex == columnCount - 1 {
transition.updateFrame(node: self.disclosureButton, frame: itemFrame)
if self.isExpanded {
if self.disclosureButton.alpha.isEqual(to: 1.0) {
self.disclosureButton.alpha = 0.0
if transition.isAnimated {
self.disclosureButton.layer.animateScale(from: 0.8, to: 0.1, duration: 0.2, removeOnCompletion: false)
self.disclosureButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.disclosureButton.layer.removeAnimation(forKey: "scale")
})
}
}
} else {
self.disclosureButton.alpha = 1.0
}
}
}
let isInOverflow = backgroundFrame.maxY > anchorRect.minY
@ -254,10 +325,10 @@ public final class ReactionContextNode: ASDisplayNode {
let largeCircleFrame: CGRect
let smallCircleFrame: CGRect
if isLeftAligned {
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 22.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 16.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
} else {
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 24.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 18.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
}
@ -289,15 +360,16 @@ public final class ReactionContextNode: ASDisplayNode {
}
public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) {
self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
for itemNode in self.itemNodes {
self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
self.disclosureButton.layer.animateAlpha(from: self.disclosureButton.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
if let targetAnchorRect = targetAnchorRect, let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: nil, animateOutToAnchorRect: targetAnchorRect)
@ -383,8 +455,14 @@ public final class ReactionContextNode: ASDisplayNode {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
if !self.disclosureButton.alpha.isZero {
if let result = self.disclosureButton.hitTest(self.disclosureButton.view.convert(point, from: self.view), with: event) {
return result
}
}
for itemNode in self.itemNodes {
if itemNode.frame.contains(point) {
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
return self.view
}
}
@ -401,13 +479,14 @@ public final class ReactionContextNode: ASDisplayNode {
}
public func reaction(at point: CGPoint) -> ReactionGestureItem? {
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
for itemNode in self.itemNodes {
if itemNode.frame.contains(point) {
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
return itemNode.reaction
}
}
for itemNode in self.itemNodes {
if itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(point) {
if !itemNode.alpha.isZero && itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(contentPoint) {
return itemNode.reaction
}
}
@ -420,4 +499,11 @@ public final class ReactionContextNode: ASDisplayNode {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
}
}
@objc private func disclosurePressed() {
self.isExpanded = true
if let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.3, curve: .spring), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
}
}
}

View File

@ -80,7 +80,7 @@ final class ReactionNode: ASDisplayNode {
switch reaction {
case let .reaction(value, _, path):
switch value {
case "😒":
case "😔":
intrinsicSize.width *= 1.7
intrinsicSize.height *= 1.7
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
@ -96,6 +96,34 @@ final class ReactionNode: ASDisplayNode {
intrinsicSize.width *= 1.256
intrinsicSize.height *= 1.256
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.05 * intrinsicSize.width)
case "🥳":
intrinsicSize.width *= 1.39
intrinsicSize.height *= 1.39
self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: -0.05 * intrinsicSize.width)
case "😭":
intrinsicSize.width *= 1.15
intrinsicSize.height *= 1.15
self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: 0.03 * intrinsicSize.width)
case "😒":
intrinsicSize.width *= 1.05
intrinsicSize.height *= 1.05
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
case "👌":
intrinsicSize.width *= 1.08
intrinsicSize.height *= 1.08
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
case "😐":
intrinsicSize.width *= 1.75
intrinsicSize.height *= 1.75
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.01 * intrinsicSize.width)
case "💩":
intrinsicSize.width *= 1.1
intrinsicSize.height *= 1.1
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
case "😊":
intrinsicSize.width *= 1.2
intrinsicSize.height *= 1.2
self.intrinsicOffset = CGPoint(x: 0.0, y: -0.01 * intrinsicSize.width)
default:
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
}
@ -116,6 +144,8 @@ final class ReactionNode: ASDisplayNode {
super.init()
//self.backgroundColor = .gray
self.textBackgroundNode.addSubnode(self.textNode)
self.addSubnode(self.textBackgroundNode)

View File

@ -477,7 +477,7 @@ public final class ShareController: ViewController {
if let error = error {
Queue.mainQueue().async {
let _ = (account.postbox.transaction { transaction -> Peer? in
transaction.deleteMessages([id])
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: [id])
return transaction.getPeer(id.peerId)
}
|> deliverOnMainQueue).start(next: { peer in

View File

@ -2281,14 +2281,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
}
}
case let .DeleteMessagesWithGlobalIds(ids):
transaction.deleteMessagesWithGlobalIds(ids)
transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
})
case let .DeleteMessages(ids):
deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids)
case let .UpdateMinAvailableMessage(id):
if let message = transaction.getMessage(id) {
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false)
}
transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id)
transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
})
case let .UpdatePeerChatInclusion(peerId, groupId, changedGroup):
let currentInclusion = transaction.getPeerChatListInclusion(peerId)
var currentPinningIndex: UInt16?

View File

@ -26,7 +26,15 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M
}
}
}
transaction.deleteMessages(ids)
transaction.deleteMessages(ids, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
})
}
public func deleteAllMessagesWithAuthor(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
transaction.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
})
}
public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId) {
@ -36,5 +44,7 @@ public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: P
return true
})
}
transaction.clearHistory(peerId)
transaction.clearHistory(peerId, forEachMedia: { media in
processRemovedMedia(mediaBox, media)
})
}

View File

@ -165,7 +165,9 @@ public func clearAuthorHistory(account: Account, peerId: PeerId, memberId: PeerI
|> `catch` { success -> Signal<Void, NoError> in
if success {
return account.postbox.transaction { transaction -> Void in
transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud)
transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud, forEachMedia: { media in
processRemovedMedia(account.postbox.mediaBox, media)
})
}
} else {
return .complete()

View File

@ -0,0 +1,15 @@
import Foundation
#if os(macOS)
import PostboxMac
#else
import Postbox
#endif
func processRemovedMedia(_ mediaBox: MediaBox, _ media: Media) {
if let image = media as? TelegramMediaImage {
let _ = mediaBox.removeCachedResources(Set(image.representations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
} else if let file = media as? TelegramMediaFile {
let _ = mediaBox.removeCachedResources(Set(file.previewRepresentations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
let _ = mediaBox.removeCachedResources(Set([WrappedMediaResourceId(file.resource.id)])).start()
}
}

View File

@ -127,7 +127,9 @@ func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Netw
})
|> then(
postbox.transaction { transaction -> Void in
transaction.deleteMessages([entry.id])
transaction.deleteMessages([entry.id], forEachMedia: { media in
processRemovedMedia(postbox.mediaBox, media)
})
}
|> ignoreValues
)

View File

@ -425,7 +425,9 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
})
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {
transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id)
transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id, forEachMedia: { media in
processRemovedMedia(postbox.mediaBox, media)
})
}
case .chatFull:
break

View File

@ -602,6 +602,8 @@
D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; };
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; };
D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; };
D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; };
D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; };
D0BB7C5A1E5C8074001527C3 /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
D0BC386E1E3FDAB70044D6FE /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */; };
D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */; };
@ -1118,6 +1120,7 @@
D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePeerNotificationSettings.swift; sourceTree = "<group>"; };
D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = "<group>"; };
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyUsedHashtags.swift; sourceTree = "<group>"; };
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessRemovedMedia.swift; sourceTree = "<group>"; };
D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelParticipants.swift; sourceTree = "<group>"; };
D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = "<group>"; };
D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeers.swift; sourceTree = "<group>"; };
@ -1513,6 +1516,7 @@
D03B0D121D62257600955575 /* Resources */,
D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */,
D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */,
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */,
);
name = Media;
sourceTree = "<group>";
@ -2484,6 +2488,7 @@
D041E3F81E535A88008C24B4 /* RemovePeerMember.swift in Sources */,
D076F8892296D8E9004F895A /* ManageChannelDiscussionGroup.swift in Sources */,
D0B417C11D7DCEEF004562A4 /* ApiGroupOrChannel.swift in Sources */,
D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */,
D041E3F51E535464008C24B4 /* AddPeerMember.swift in Sources */,
D0AF32311FACEDEC0097362B /* CoreSettings.swift in Sources */,
D054649A20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */,
@ -2875,6 +2880,7 @@
D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */,
D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */,
D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */,
D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */,
D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */,
D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */,
D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */,

View File

@ -530,11 +530,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let reactions: [(String, String, String)] = [
("😒", "Sad", "sad"),
("😔", "Sad", "sad"),
("😳", "Surprised", "surprised"),
("😂", "Fun", "lol"),
("👍", "Like", "thumbsup"),
("", "Love", "heart"),
("🥳", "Celebrate", "celebrate"),
("😭", "Cry", "cry"),
("😒", "Meh", "meh"),
("👌", "OK", "ok"),
("😐", "Poker", "poker"),
("💩", "Poop", "poop"),
("😊", "Smile", "smile")
]
var reactionItems: [ReactionContextItem] = []
@ -6971,8 +6978,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if actions.contains(3) {
let mediaBox = strongSelf.context.account.postbox.mediaBox
let _ = strongSelf.context.account.postbox.transaction({ transaction -> Void in
transaction.removeAllMessagesWithAuthor(peerId, authorId: author.id, namespace: Namespaces.Message.Cloud)
deleteAllMessagesWithAuthor(transaction: transaction, mediaBox: mediaBox, peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud)
}).start()
let _ = clearAuthorHistory(account: strongSelf.context.account, peerId: peerId, memberId: author.id).start()
} else if actions.contains(0) {

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import AccountContext
private let dateFont = UIFont.italicSystemFont(ofSize: 11.0)
private let reactionCountFont = Font.semiboldItalic(11.0)
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
if let _ = layer.animation(forKey: "clockFrameAnimation") {
@ -62,50 +63,10 @@ enum ChatMessageDateAndStatusType: Equatable {
case ImageOutgoing(ChatMessageDateAndStatusOutgoingType)
case FreeIncoming
case FreeOutgoing(ChatMessageDateAndStatusOutgoingType)
static func ==(lhs: ChatMessageDateAndStatusType, rhs: ChatMessageDateAndStatusType) -> Bool {
switch lhs {
case .BubbleIncoming:
if case .BubbleIncoming = rhs {
return true
} else {
return false
}
case let .BubbleOutgoing(type):
if case .BubbleOutgoing(type) = rhs {
return true
} else {
return false
}
case .ImageIncoming:
if case .ImageIncoming = rhs {
return true
} else {
return false
}
case let .ImageOutgoing(type):
if case .ImageOutgoing(type) = rhs {
return true
} else {
return false
}
case .FreeIncoming:
if case .FreeIncoming = rhs {
return true
} else {
return false
}
case let .FreeOutgoing(type):
if case .FreeOutgoing(type) = rhs {
return true
} else {
return false
}
}
}
}
private let reactionSize: CGFloat = 18.0
private let reactionSize: CGFloat = 19.0
private let reactionFont = Font.regular(12.0)
private final class StatusReactionNode: ASImageNode {
let value: String
@ -120,7 +81,7 @@ private final class StatusReactionNode: ASImageNode {
self.image = generateImage(CGSize(width: reactionSize, height: reactionSize), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
let string = NSAttributedString(string: value, font: Font.regular(11.0), textColor: .black)
let string = NSAttributedString(string: value, font: reactionFont, textColor: .black)
string.draw(at: CGPoint(x: 1.0, y: 3.0))
UIGraphicsPopContext()
})
@ -136,6 +97,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private let dateNode: TextNode
private var impressionIcon: ASImageNode?
private var reactionNodes: [StatusReactionNode] = []
private var reactionCountNode: TextNode?
private var type: ChatMessageDateAndStatusType?
private var theme: ChatPresentationThemeData?
@ -166,6 +128,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let currentType = self.type
let currentTheme = self.theme
let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode)
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in
let dateColor: UIColor
var backgroundImage: UIImage?
@ -396,9 +360,20 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
backgroundInsets = UIEdgeInsets(top: 2.0, left: 7.0, bottom: 2.0, right: 7.0)
}
var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
var reactionInset: CGFloat = 0.0
if !reactions.isEmpty {
reactionInset = 1.0 + CGFloat(reactions.count) * reactionSize
reactionInset = 5.0 + CGFloat(reactions.count) * reactionSize
var count = 0
for reaction in reactions {
count += Int(reaction.count)
}
let layoutAndApply = makeReactionCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(count)", font: reactionCountFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
reactionInset += layoutAndApply.0.size.width + 2.0
reactionCountLayoutAndApply = layoutAndApply
}
leftInset += reactionInset
@ -543,7 +518,14 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} else {
node = StatusReactionNode(value: reactions[i].value, count: Int(reactions[i].count))
if strongSelf.reactionNodes.count > i {
strongSelf.reactionNodes[i].removeFromSupernode()
let previousNode = strongSelf.reactionNodes[i]
if animated {
previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in
previousNode?.removeFromSupernode()
})
} else {
previousNode.removeFromSupernode()
}
strongSelf.reactionNodes[i] = node
} else {
strongSelf.reactionNodes.append(node)
@ -551,12 +533,44 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
if node.supernode == nil {
strongSelf.addSubnode(node)
if animated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + 1.0 + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
reactionOffset += reactionSize
}
for _ in reactions.count ..< strongSelf.reactionNodes.count {
strongSelf.reactionNodes.removeLast().removeFromSupernode()
let node = strongSelf.reactionNodes.removeLast()
if animated {
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
node?.removeFromSupernode()
})
} else {
node.removeFromSupernode()
}
}
if let (layout, apply) = reactionCountLayoutAndApply {
let node = apply()
if strongSelf.reactionCountNode !== node {
strongSelf.reactionCountNode?.removeFromSupernode()
strongSelf.addSubnode(node)
strongSelf.reactionCountNode = node
if animated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
} else if let reactionCountNode = strongSelf.reactionCountNode {
strongSelf.reactionCountNode = nil
if animated {
reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in
reactionCountNode?.removeFromSupernode()
})
} else {
reactionCountNode.removeFromSupernode()
}
}
}
})

View File

@ -234,60 +234,36 @@ public final class SharedNotificationManager {
title = alertTitle
}
}
if let locKey = alert["loc-key"] as? String {
if locKey == "SESSION_REVOKE" {
isForcedLogOut = true
} else if locKey == "PHONE_CALL_REQUEST" {
isCall = true
} else if locKey == "GEO_LIVE_PENDING" {
isLocationPolling = true
} else if locKey == "MESSAGE_MUTED" {
shouldPollState = true
} else if locKey == "MESSAGE_DELETED" {
var peerId: PeerId?
if let fromId = payload["from_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["chat_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["channel_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
}
if let peerId = peerId {
if let messageIds = payload["messages"] as? String {
for messageId in messageIds.split(separator: ",") {
if let messageIdValue = Int32(messageId) {
messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue))
}
}
}
}
}
if let locKey = payload["loc-key"] as? String {
if locKey == "SESSION_REVOKE" {
isForcedLogOut = true
} else if locKey == "PHONE_CALL_REQUEST" {
isCall = true
} else if locKey == "GEO_LIVE_PENDING" {
isLocationPolling = true
} else if locKey == "MESSAGE_MUTED" {
shouldPollState = true
} else if locKey == "MESSAGE_DELETED" {
var peerId: PeerId?
if let fromId = payload["from_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["chat_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["channel_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
}
let string = NSLocalizedString(locKey, comment: "")
if !string.isEmpty {
if let locArgs = alert["loc-args"] as? [AnyObject] {
var args: [CVarArg] = []
var failed = false
for arg in locArgs {
if let arg = arg as? CVarArg {
args.append(arg)
} else {
failed = true
break
if let peerId = peerId {
if let messageIds = payload["messages"] as? String {
for messageId in messageIds.split(separator: ",") {
if let messageIdValue = Int32(messageId) {
messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue))
}
}
if failed {
body = "\(string)"
} else {
body = String(format: string, arguments: args)
}
} else {
body = "\(string)"
}
} else {
body = nil
}
}
}
@ -419,7 +395,7 @@ public final class SharedNotificationManager {
if !messagesDeleted.isEmpty {
let _ = account.postbox.transaction(ignoreDisabled: true, { transaction -> Void in
transaction.deleteMessages(messagesDeleted)
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: messagesDeleted)
}).start()
}

View File

@ -1,6 +1,11 @@
import Foundation
public func doesUrlMatchText(url: String, text: String) -> Bool {
for c in url {
if !c.isASCII {
return false
}
}
if url == text {
return true
}
@ -32,7 +37,6 @@ public extension CharacterSet {
}
public func isValidUrl(_ url: String) -> Bool {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), ["http", "https"].contains(url.scheme), let host = url.host, host.contains(".") && url.user == nil {
let components = host.components(separatedBy: ".")
let domain = (components.first ?? "")