mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Display all reactions in context menu
This commit is contained in:
parent
47c4ab17a3
commit
5754dfb307
@ -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]) {
|
||||
|
@ -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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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: ¤tUnsentOperations, 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]]) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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 */,
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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 ?? "")
|
||||
|
Loading…
x
Reference in New Issue
Block a user