mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Reactions update [skip ci]
This commit is contained in:
parent
e694aa319d
commit
f47fa686e9
@ -7250,3 +7250,5 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Group.Members.Contacts" = "CONTACTS IN THIS GROUP";
|
"Group.Members.Contacts" = "CONTACTS IN THIS GROUP";
|
||||||
"Group.Members.Other" = "OTHERS MEMBERS";
|
"Group.Members.Other" = "OTHERS MEMBERS";
|
||||||
|
|
||||||
|
"Conversation.ReadAllReactions" = "Read All Reactions";
|
||||||
|
@ -1481,12 +1481,12 @@ final class MessageHistoryTable: Table {
|
|||||||
let updatedGroupInfo = self.updateMovingGroupInfoInNamespace(index: updatedIndex, updatedIndex: updatedIndex, groupingKey: message.groupingKey, previousInfo: previousMessage.groupInfo, updatedGroupInfos: &updatedGroupInfos)
|
let updatedGroupInfo = self.updateMovingGroupInfoInNamespace(index: updatedIndex, updatedIndex: updatedIndex, groupingKey: message.groupingKey, previousInfo: previousMessage.groupInfo, updatedGroupInfos: &updatedGroupInfos)
|
||||||
|
|
||||||
if previousMessage.tags != message.tags || index != updatedIndex {
|
if previousMessage.tags != message.tags || index != updatedIndex {
|
||||||
let isNewlyAdded = previousMessage.tags.isEmpty
|
|
||||||
if !previousMessage.tags.isEmpty {
|
if !previousMessage.tags.isEmpty {
|
||||||
self.tagsTable.remove(tags: previousMessage.tags, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries)
|
self.tagsTable.remove(tags: previousMessage.tags, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries)
|
||||||
}
|
}
|
||||||
if !message.tags.isEmpty {
|
if !message.tags.isEmpty {
|
||||||
self.tagsTable.add(tags: message.tags, index: message.index, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries)
|
//let isNewlyAdded = previousMessage.tags.isEmpty
|
||||||
|
self.tagsTable.add(tags: message.tags, index: message.index, isNewlyAdded: false, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if previousMessage.threadId != message.threadId || index != message.index {
|
if previousMessage.threadId != message.threadId || index != message.index {
|
||||||
|
@ -133,7 +133,7 @@ class MessageHistoryTagsSummaryTable: Table {
|
|||||||
func removeMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) {
|
func removeMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) {
|
||||||
if let current = self.get(key) {
|
if let current = self.get(key) {
|
||||||
if current.count == 0 {
|
if current.count == 0 {
|
||||||
self.invalidateTable.insert(InvalidatedMessageHistoryTagsSummaryKey(peerId: key.peerId, namespace: key.namespace, tagMask: key.tag), operations: &invalidateSummaries)
|
//self.invalidateTable.insert(InvalidatedMessageHistoryTagsSummaryKey(peerId: key.peerId, namespace: key.namespace, tagMask: key.tag), operations: &invalidateSummaries)
|
||||||
} else {
|
} else {
|
||||||
self.set(key, summary: current.withAddedCount(-1), updatedSummaries: &updatedSummaries)
|
self.set(key, summary: current.withAddedCount(-1), updatedSummaries: &updatedSummaries)
|
||||||
}
|
}
|
||||||
|
@ -1086,6 +1086,7 @@ public class Account {
|
|||||||
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedReadReactionActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedReadReactionActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
|
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenReactionsOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||||
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
|
@ -157,6 +157,7 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) })
|
declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) })
|
||||||
declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) })
|
declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) })
|
||||||
declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) })
|
declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) })
|
||||||
|
declareEncodable(SynchronizeMarkAllUnseenReactionsOperation.self, f: { SynchronizeMarkAllUnseenReactionsOperation(decoder: $0) })
|
||||||
declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) })
|
declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) })
|
||||||
declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) })
|
declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) })
|
||||||
declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) })
|
declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) })
|
||||||
|
@ -3313,39 +3313,6 @@ func replayFinalState(
|
|||||||
attributes.append(updatedReactions)
|
attributes.append(updatedReactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let eventTimestamp = eventTimestamp, !currentMessage.flags.contains(.Incoming), let chatPeer = currentMessage.peers[currentMessage.id.peerId] {
|
|
||||||
let _ = chatPeer
|
|
||||||
|
|
||||||
var previousCount = 0
|
|
||||||
if let previousReactions = previousReactions {
|
|
||||||
for reaction in previousReactions.reactions {
|
|
||||||
previousCount += Int(reaction.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedCount = 0
|
|
||||||
for reaction in updatedReactions.reactions {
|
|
||||||
updatedCount += Int(reaction.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedCount > previousCount {
|
|
||||||
if let topPeer = updatedReactions.recentPeers.last {
|
|
||||||
var wasPresentBefore = false
|
|
||||||
if let previousReactions = previousReactions {
|
|
||||||
for recentPeer in previousReactions.recentPeers {
|
|
||||||
if recentPeer.peerId == topPeer.peerId {
|
|
||||||
wasPresentBefore = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !wasPresentBefore, let reactionAuthor = transaction.getPeer(topPeer.peerId), transaction.isPeerContact(peerId: topPeer.peerId) {
|
|
||||||
generatedEvent = (reactionAuthor: reactionAuthor, message: currentMessage.withUpdatedAttributes(attributes), timestamp: eventTimestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tags = currentMessage.tags
|
var tags = currentMessage.tags
|
||||||
if updatedReactions.hasUnseen {
|
if updatedReactions.hasUnseen {
|
||||||
tags.insert(.unseenReaction)
|
tags.insert(.unseenReaction)
|
||||||
|
@ -1228,6 +1228,29 @@ public final class AccountViewTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateMarkAllReactionsSeen(peerId: PeerId) {
|
||||||
|
self.queue.async {
|
||||||
|
guard let account = self.account else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = (account.postbox.transaction { transaction -> Set<MessageId> in
|
||||||
|
let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id }))
|
||||||
|
if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud), summary.count > 0 {
|
||||||
|
var maxId: Int32 = summary.range.maxId
|
||||||
|
if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
|
||||||
|
maxId = index.id.id
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: 0, maxId: maxId)
|
||||||
|
addSynchronizeMarkAllUnseenReactionsOperation(transaction: transaction, peerId: peerId, maxId: summary.range.maxId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|> deliverOn(self.queue)).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func updateMarkReactionsSeenForMessageIds(messageIds: Set<MessageId>) {
|
public func updateMarkReactionsSeenForMessageIds(messageIds: Set<MessageId>) {
|
||||||
self.queue.async {
|
self.queue.async {
|
||||||
let addedMessageIds: [MessageId] = Array(messageIds)
|
let addedMessageIds: [MessageId] = Array(messageIds)
|
||||||
|
@ -283,40 +283,71 @@ private func synchronizeConsumeMessageContents(transaction: Transaction, postbox
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func synchronizeReadMessageReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal<Void, NoError> {
|
private func synchronizeReadMessageReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal<Void, NoError> {
|
||||||
guard let inputPeer = transaction.getPeer(id.peerId).flatMap(apiInputPeer) else {
|
if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||||
return .complete()
|
return network.request(Api.functions.messages.readMessageContents(id: [id.id]))
|
||||||
}
|
|> map(Optional.init)
|
||||||
return network.request(Api.functions.messages.readReactions(peer: inputPeer))
|
|> `catch` { _ -> Signal<Api.messages.AffectedMessages?, NoError> in
|
||||||
|> map(Optional.init)
|
return .single(nil)
|
||||||
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, NoError> in
|
}
|
||||||
return .single(nil)
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||||
}
|
if let result = result {
|
||||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
switch result {
|
||||||
if let result = result {
|
case let .affectedMessages(pts, ptsCount):
|
||||||
switch result {
|
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||||
case let .affectedHistory(pts, ptsCount, _):
|
}
|
||||||
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
}
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.setPendingMessageAction(type: .readReaction, id: id, action: nil)
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||||
|
}
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ReactionsMessageAttribute, attribute.hasUnseen {
|
||||||
|
attributes[j] = attribute.withAllSeen()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var updatedTags = currentMessage.tags
|
||||||
|
updatedTags.remove(.unseenReaction)
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return postbox.transaction { transaction -> Void in
|
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
transaction.setPendingMessageAction(type: .readReaction, id: id, action: nil)
|
if let peer = transaction.getPeer(id.peerId), let inputChannel = apiInputChannel(peer) {
|
||||||
transaction.updateMessage(id, update: { currentMessage in
|
return network.request(Api.functions.channels.readMessageContents(channel: inputChannel, id: [id.id]))
|
||||||
var storeForwardInfo: StoreMessageForwardInfo?
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
if let forwardInfo = currentMessage.forwardInfo {
|
return .single(.boolFalse)
|
||||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
}
|
||||||
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.setPendingMessageAction(type: .readReaction, id: id, action: nil)
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||||
|
}
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ReactionsMessageAttribute, attribute.hasUnseen {
|
||||||
|
attributes[j] = attribute.withAllSeen()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var updatedTags = currentMessage.tags
|
||||||
|
updatedTags.remove(.unseenReaction)
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
var attributes = currentMessage.attributes
|
}
|
||||||
loop: for j in 0 ..< attributes.count {
|
} else {
|
||||||
if let attribute = attributes[j] as? ReactionsMessageAttribute, attribute.hasUnseen {
|
return .complete()
|
||||||
attributes[j] = attribute.withAllSeen()
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var updatedTags = currentMessage.tags
|
|
||||||
updatedTags.remove(.unseenReaction)
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import MtProtoKit
|
import MtProtoKit
|
||||||
|
|
||||||
|
|
||||||
private final class ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper {
|
private final class ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper {
|
||||||
var operationDisposables: [Int32: Disposable] = [:]
|
var operationDisposables: [Int32: Disposable] = [:]
|
||||||
|
|
||||||
@ -49,11 +48,11 @@ private final class ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
|
private func withTakenOperation<T>(postbox: Postbox, peerId: PeerId, operationType: T.Type, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
|
||||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
var result: PeerMergedOperationLogEntry?
|
var result: PeerMergedOperationLogEntry?
|
||||||
transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in
|
||||||
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeMarkAllUnseenPersonalMessagesOperation {
|
if let entry = entry, let _ = entry.mergedIndex, entry.contents is T {
|
||||||
result = entry.mergedEntry!
|
result = entry.mergedEntry!
|
||||||
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
||||||
} else {
|
} else {
|
||||||
@ -62,7 +61,8 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOpera
|
|||||||
})
|
})
|
||||||
|
|
||||||
return f(transaction, result)
|
return f(transaction, result)
|
||||||
} |> switchToLatest
|
}
|
||||||
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||||
@ -81,7 +81,7 @@ func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (entry, disposable) in beginOperations {
|
for (entry, disposable) in beginOperations {
|
||||||
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, operationType: SynchronizeMarkAllUnseenPersonalMessagesOperation.self, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
||||||
if let entry = entry {
|
if let entry = entry {
|
||||||
if let operation = entry.contents as? SynchronizeMarkAllUnseenPersonalMessagesOperation {
|
if let operation = entry.contents as? SynchronizeMarkAllUnseenPersonalMessagesOperation {
|
||||||
return synchronizeMarkAllUnseen(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, peerId: entry.peerId, operation: operation)
|
return synchronizeMarkAllUnseen(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, peerId: entry.peerId, operation: operation)
|
||||||
@ -233,6 +233,141 @@ func markUnseenPersonalMessage(transaction: Transaction, id: MessageId, addSynch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func managedSynchronizeMarkAllUnseenReactionsOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||||
|
return Signal { _ in
|
||||||
|
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenReactions
|
||||||
|
|
||||||
|
let helper = Atomic<ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper>(value: ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper())
|
||||||
|
|
||||||
|
let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in
|
||||||
|
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
|
||||||
|
return helper.update(view.entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
for disposable in disposeOperations {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entry, disposable) in beginOperations {
|
||||||
|
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, operationType: SynchronizeMarkAllUnseenReactionsOperation.self, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
||||||
|
if let entry = entry {
|
||||||
|
if let operation = entry.contents as? SynchronizeMarkAllUnseenReactionsOperation {
|
||||||
|
return synchronizeMarkAllUnseenReactions(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, peerId: entry.peerId, operation: operation)
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
})
|
||||||
|
|> then(postbox.transaction { transaction -> Void in
|
||||||
|
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
disposable.set(signal.start())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
let disposables = helper.with { helper -> [Disposable] in
|
||||||
|
return helper.reset()
|
||||||
|
}
|
||||||
|
for disposable in disposables {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, operation: SynchronizeMarkAllUnseenReactionsOperation) -> Signal<Void, NoError> {
|
||||||
|
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||||
|
let limit: Int32 = 100
|
||||||
|
let oneOperation: (Int32) -> Signal<Int32?, MTRpcError> = { maxId in
|
||||||
|
return network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1))
|
||||||
|
|> mapToSignal { result -> Signal<[MessageId], MTRpcError> in
|
||||||
|
switch result {
|
||||||
|
case let .messages(messages, _, _):
|
||||||
|
return .single(messages.compactMap({ $0.id() }))
|
||||||
|
case let .channelMessages(_, _, _, _, messages, _, _):
|
||||||
|
return .single(messages.compactMap({ $0.id() }))
|
||||||
|
case .messagesNotModified:
|
||||||
|
return .single([])
|
||||||
|
case let .messagesSlice(_, _, _, _, messages, _, _):
|
||||||
|
return .single(messages.compactMap({ $0.id() }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { ids -> Signal<Int32?, MTRpcError> in
|
||||||
|
let filteredIds = ids.filter { $0.id <= operation.maxId }
|
||||||
|
if filteredIds.isEmpty {
|
||||||
|
return .single(ids.min()?.id)
|
||||||
|
}
|
||||||
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
guard let inputChannel = inputChannel else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return network.request(Api.functions.channels.readMessageContents(channel: inputChannel, id: filteredIds.map { $0.id }))
|
||||||
|
|> map { result -> Int32? in
|
||||||
|
if ids.count < limit {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return ids.min()?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return network.request(Api.functions.messages.readMessageContents(id: filteredIds.map { $0.id }))
|
||||||
|
|> map { result -> Int32? in
|
||||||
|
switch result {
|
||||||
|
case let .affectedMessages(pts, ptsCount):
|
||||||
|
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||||
|
}
|
||||||
|
if ids.count < limit {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return ids.min()?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let currentMaxId = Atomic<Int32>(value: 0)
|
||||||
|
let loopOperations: Signal<Void, GetUnseenIdsError> = (
|
||||||
|
(
|
||||||
|
deferred {
|
||||||
|
return oneOperation(currentMaxId.with { $0 })
|
||||||
|
}
|
||||||
|
|> `catch` { error -> Signal<Int32?, GetUnseenIdsError> in
|
||||||
|
return .fail(.error(error))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> mapToSignal { resultId -> Signal<Void, GetUnseenIdsError> in
|
||||||
|
if let resultId = resultId {
|
||||||
|
let previous = currentMaxId.swap(resultId)
|
||||||
|
if previous == resultId {
|
||||||
|
return .fail(.done)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> `catch` { error -> Signal<Void, GetUnseenIdsError> in
|
||||||
|
switch error {
|
||||||
|
case .done, .error:
|
||||||
|
return .fail(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> restart
|
||||||
|
)
|
||||||
|
return loopOperations
|
||||||
|
|> `catch` { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) {
|
func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) {
|
||||||
if let message = transaction.getMessage(id) {
|
if let message = transaction.getMessage(id) {
|
||||||
var consume = false
|
var consume = false
|
||||||
|
@ -2,7 +2,6 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
|
||||||
func addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: Transaction, peerId: PeerId, maxId: MessageId.Id) {
|
func addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: Transaction, peerId: PeerId, maxId: MessageId.Id) {
|
||||||
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenPersonalMessages
|
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenPersonalMessages
|
||||||
|
|
||||||
@ -25,3 +24,25 @@ func addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: Transacti
|
|||||||
|
|
||||||
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeMarkAllUnseenPersonalMessagesOperation(maxId: maxId))
|
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeMarkAllUnseenPersonalMessagesOperation(maxId: maxId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addSynchronizeMarkAllUnseenReactionsOperation(transaction: Transaction, peerId: PeerId, maxId: MessageId.Id) {
|
||||||
|
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenReactions
|
||||||
|
var topLocalIndex: Int32?
|
||||||
|
var currentMaxId: MessageId.Id?
|
||||||
|
transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in
|
||||||
|
topLocalIndex = entry.tagLocalIndex
|
||||||
|
if let operation = entry.contents as? SynchronizeMarkAllUnseenReactionsOperation {
|
||||||
|
currentMaxId = operation.maxId
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if let topLocalIndex = topLocalIndex {
|
||||||
|
if let currentMaxId = currentMaxId, currentMaxId >= maxId {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeMarkAllUnseenReactionsOperation(maxId: maxId))
|
||||||
|
}
|
||||||
|
@ -156,6 +156,7 @@ public struct OperationLogTags {
|
|||||||
public static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18)
|
public static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18)
|
||||||
public static let SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
|
public static let SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
|
||||||
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
||||||
|
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||||
|
@ -15,3 +15,19 @@ public final class SynchronizeMarkAllUnseenPersonalMessagesOperation: PostboxCod
|
|||||||
encoder.encodeInt32(self.maxId, forKey: "maxId")
|
encoder.encodeInt32(self.maxId, forKey: "maxId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class SynchronizeMarkAllUnseenReactionsOperation: PostboxCoding {
|
||||||
|
public let maxId: MessageId.Id
|
||||||
|
|
||||||
|
public init(maxId: MessageId.Id) {
|
||||||
|
self.maxId = maxId
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.maxId = decoder.decodeInt32ForKey("maxId", orElse: Int32.min + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeInt32(self.maxId, forKey: "maxId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -166,6 +166,16 @@ public func clearPeerUnseenPersonalMessagesInteractively(account: Account, peerI
|
|||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func clearPeerUnseenReactionsInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account.viewTracker.updateMarkAllReactionsSeen(peerId: peerId)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
public func markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) {
|
public func markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) {
|
||||||
for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: filterPredicate) {
|
for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: filterPredicate) {
|
||||||
togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: false)
|
togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: false)
|
||||||
|
@ -5842,26 +5842,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.chatDisplayNode.navigateButtons.mentionsMenu = { [weak self] in
|
self.chatDisplayNode.navigateButtons.mentionsButton.activated = { [weak self] gesture, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
gesture.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.WebSearch_RecentSectionClear, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
var menuItems: [ContextMenuItem] = []
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(
|
||||||
|
id: nil,
|
||||||
|
text: strongSelf.presentationData.strings.WebSearch_RecentSectionClear,
|
||||||
|
textColor: .primary,
|
||||||
|
textLayout: .singleLine,
|
||||||
|
icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Read"), color: theme.contextMenu.primaryColor)
|
||||||
|
},
|
||||||
|
action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = clearPeerUnseenPersonalMessagesInteractively(account: strongSelf.context.account, peerId: peerId).start()
|
let _ = clearPeerUnseenPersonalMessagesInteractively(account: strongSelf.context.account, peerId: peerId).start()
|
||||||
})
|
}
|
||||||
]), ActionSheetItemGroup(items: [
|
)))
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
let items = ContextController.Items(content: .list(menuItems))
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageNavigationButtonContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, contentNode: strongSelf.chatDisplayNode.navigateButtons.mentionsButton.containerNode)), items: .single(items), recognizer: nil, gesture: gesture)
|
||||||
])])
|
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.forEachController({ controller in
|
||||||
strongSelf.present(actionSheet, in: .window(.root))
|
if let controller = controller as? TooltipScreen {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
strongSelf.window?.presentInGlobalOverlay(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.chatDisplayNode.navigateButtons.reactionsPressed = { [weak self] in
|
self.chatDisplayNode.navigateButtons.reactionsPressed = { [weak self] in
|
||||||
@ -5872,7 +5889,72 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
switch result {
|
switch result {
|
||||||
case let .result(messageId):
|
case let .result(messageId):
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil))
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), completion: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||||
|
guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard item.message.id == messageId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var maybeUpdatedReaction: String?
|
||||||
|
if let attribute = item.message.reactionsAttribute {
|
||||||
|
for recentPeer in attribute.recentPeers {
|
||||||
|
if recentPeer.isUnseen {
|
||||||
|
maybeUpdatedReaction = recentPeer.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let updatedReaction = maybeUpdatedReaction else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for reaction in availableReactions.reactions {
|
||||||
|
guard let centerAnimation = reaction.centerAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reaction.value == updatedReaction {
|
||||||
|
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||||
|
|
||||||
|
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||||
|
|
||||||
|
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||||
|
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
|
||||||
|
standaloneReactionAnimation.animateReactionSelection(
|
||||||
|
context: strongSelf.context,
|
||||||
|
theme: strongSelf.presentationData.theme,
|
||||||
|
reaction: ReactionContextItem(
|
||||||
|
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
||||||
|
appearAnimation: reaction.appearAnimation,
|
||||||
|
stillAnimation: reaction.selectAnimation,
|
||||||
|
listAnimation: centerAnimation,
|
||||||
|
largeListAnimation: reaction.activateAnimation,
|
||||||
|
applicationAnimation: aroundAnimation,
|
||||||
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
|
),
|
||||||
|
targetView: targetView,
|
||||||
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
|
standaloneReactionAnimation?.removeFromSupernode()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
case .loading:
|
case .loading:
|
||||||
break
|
break
|
||||||
@ -5882,6 +5964,45 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatDisplayNode.navigateButtons.reactionsButton.activated = { [weak self] gesture, _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
gesture.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||||
|
|
||||||
|
var menuItems: [ContextMenuItem] = []
|
||||||
|
menuItems.append(.action(ContextMenuActionItem(
|
||||||
|
id: nil,
|
||||||
|
text: strongSelf.presentationData.strings.Conversation_ReadAllReactions,
|
||||||
|
textColor: .primary,
|
||||||
|
textLayout: .singleLine,
|
||||||
|
icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Read"), color: theme.contextMenu.primaryColor)
|
||||||
|
},
|
||||||
|
action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = clearPeerUnseenReactionsInteractively(account: strongSelf.context.account, peerId: peerId).start()
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
let items = ContextController.Items(content: .list(menuItems))
|
||||||
|
|
||||||
|
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageNavigationButtonContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, contentNode: strongSelf.chatDisplayNode.navigateButtons.reactionsButton.containerNode)), items: .single(items), recognizer: nil, gesture: gesture)
|
||||||
|
|
||||||
|
strongSelf.forEachController({ controller in
|
||||||
|
if let controller = controller as? TooltipScreen {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
strongSelf.window?.presentInGlobalOverlay(controller)
|
||||||
|
}
|
||||||
|
|
||||||
let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId, completion in
|
let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId, completion in
|
||||||
guard let strongSelf = self, strongSelf.isNodeLoaded else {
|
guard let strongSelf = self, strongSelf.isNodeLoaded else {
|
||||||
return
|
return
|
||||||
@ -12385,7 +12506,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.loadingMessage.set(.single(nil))
|
self.loadingMessage.set(.single(nil))
|
||||||
self.messageIndexDisposable.set(nil)
|
self.messageIndexDisposable.set(nil)
|
||||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
||||||
completion?()
|
Queue.mainQueue().after(0.25, {
|
||||||
|
completion?()
|
||||||
|
})
|
||||||
|
|
||||||
if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
|
if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
|
||||||
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))
|
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||||
|
@ -732,7 +732,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strongSelf.canReadHistoryValue {
|
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||||
strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds)
|
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds)
|
||||||
@ -1342,7 +1342,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds)
|
context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.canReadHistoryValue && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
|
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
|
||||||
let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen
|
let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen
|
||||||
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
|
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
|
||||||
context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||||
|
@ -12,7 +12,9 @@ enum ChatHistoryNavigationButtonType {
|
|||||||
case reactions
|
case reactions
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatHistoryNavigationButtonNode: ASControlNode {
|
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
|
let containerNode: ContextExtractedContentContainingNode
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundNode: NavigationBackgroundNode
|
||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
private let badgeBackgroundNode: ASImageNode
|
private let badgeBackgroundNode: ASImageNode
|
||||||
@ -22,9 +24,9 @@ class ChatHistoryNavigationButtonNode: ASControlNode {
|
|||||||
didSet {
|
didSet {
|
||||||
if (oldValue != nil) != (self.tapped != nil) {
|
if (oldValue != nil) != (self.tapped != nil) {
|
||||||
if self.tapped != nil {
|
if self.tapped != nil {
|
||||||
self.addTarget(self, action: #selector(onTap), forControlEvents: .touchUpInside)
|
self.buttonNode.addTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside)
|
||||||
} else {
|
} else {
|
||||||
self.removeTarget(self, action: #selector(onTap), forControlEvents: .touchUpInside)
|
self.buttonNode.removeTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,6 +46,9 @@ class ChatHistoryNavigationButtonNode: ASControlNode {
|
|||||||
init(theme: PresentationTheme, type: ChatHistoryNavigationButtonType) {
|
init(theme: PresentationTheme, type: ChatHistoryNavigationButtonType) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
|
self.containerNode = ContextExtractedContentContainingNode()
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor)
|
self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor)
|
||||||
|
|
||||||
@ -71,18 +76,30 @@ class ChatHistoryNavigationButtonNode: ASControlNode {
|
|||||||
self.badgeTextNode.displaysAsynchronously = false
|
self.badgeTextNode.displaysAsynchronously = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
|
|
||||||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: 38.0 / 2.0, transition: .immediate)
|
|
||||||
|
|
||||||
self.addSubnode(self.imageNode)
|
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
|
|
||||||
|
|
||||||
self.addSubnode(self.badgeBackgroundNode)
|
self.targetNodeForActivationProgress = self.buttonNode
|
||||||
self.addSubnode(self.badgeTextNode)
|
|
||||||
|
|
||||||
self.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
|
let size = CGSize(width: 38.0, height: 38.0)
|
||||||
|
|
||||||
|
self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
|
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.containerNode.contentNode.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.addSubnode(self.backgroundNode)
|
||||||
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: size.width / 2.0, transition: .immediate)
|
||||||
|
|
||||||
|
self.buttonNode.addSubnode(self.imageNode)
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
|
self.buttonNode.addSubnode(self.badgeBackgroundNode)
|
||||||
|
self.buttonNode.addSubnode(self.badgeTextNode)
|
||||||
|
|
||||||
|
self.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
func updateTheme(theme: PresentationTheme) {
|
||||||
|
@ -8,10 +8,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var dateTimeFormat: PresentationDateTimeFormat
|
private var dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
|
||||||
private let reactionsButton: ChatHistoryNavigationButtonNode
|
let reactionsButton: ChatHistoryNavigationButtonNode
|
||||||
private let reactionsButtonTapNode: ASDisplayNode
|
let mentionsButton: ChatHistoryNavigationButtonNode
|
||||||
private let mentionsButton: ChatHistoryNavigationButtonNode
|
|
||||||
private let mentionsButtonTapNode: ASDisplayNode
|
|
||||||
private let downButton: ChatHistoryNavigationButtonNode
|
private let downButton: ChatHistoryNavigationButtonNode
|
||||||
|
|
||||||
var downPressed: (() -> Void)? {
|
var downPressed: (() -> Void)? {
|
||||||
@ -21,9 +19,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var reactionsPressed: (() -> Void)?
|
var reactionsPressed: (() -> Void)?
|
||||||
var reactionsMenu: (() -> Void)?
|
|
||||||
var mentionsPressed: (() -> Void)?
|
var mentionsPressed: (() -> Void)?
|
||||||
var mentionsMenu: (() -> Void)?
|
|
||||||
|
|
||||||
var displayDownButton: Bool = false {
|
var displayDownButton: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
@ -78,12 +74,10 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
self.mentionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .mentions)
|
self.mentionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .mentions)
|
||||||
self.mentionsButton.alpha = 0.0
|
self.mentionsButton.alpha = 0.0
|
||||||
self.mentionsButton.isHidden = true
|
self.mentionsButton.isHidden = true
|
||||||
self.mentionsButtonTapNode = ASDisplayNode()
|
|
||||||
|
|
||||||
self.reactionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .reactions)
|
self.reactionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .reactions)
|
||||||
self.reactionsButton.alpha = 0.0
|
self.reactionsButton.alpha = 0.0
|
||||||
self.reactionsButton.isHidden = true
|
self.reactionsButton.isHidden = true
|
||||||
self.reactionsButtonTapNode = ASDisplayNode()
|
|
||||||
|
|
||||||
self.downButton = ChatHistoryNavigationButtonNode(theme: theme, type: .down)
|
self.downButton = ChatHistoryNavigationButtonNode(theme: theme, type: .down)
|
||||||
self.downButton.alpha = 0.0
|
self.downButton.alpha = 0.0
|
||||||
@ -91,29 +85,23 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.mentionsButton.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.addSubnode(self.reactionsButton)
|
self.addSubnode(self.reactionsButton)
|
||||||
self.addSubnode(self.reactionsButtonTapNode)
|
|
||||||
self.addSubnode(self.mentionsButton)
|
self.addSubnode(self.mentionsButton)
|
||||||
self.addSubnode(self.mentionsButtonTapNode)
|
|
||||||
self.addSubnode(self.downButton)
|
self.addSubnode(self.downButton)
|
||||||
|
|
||||||
|
self.reactionsButton.tapped = { [weak self] in
|
||||||
|
self?.reactionsPressed?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mentionsButton.tapped = { [weak self] in
|
||||||
|
self?.mentionsPressed?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.downButton.isGestureEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let reactionsTapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.reactionsTap(_:)))
|
|
||||||
reactionsTapRecognizer.tapActionAtPoint = { _ in
|
|
||||||
return .waitForSingleTap
|
|
||||||
}
|
|
||||||
self.reactionsButtonTapNode.view.addGestureRecognizer(reactionsTapRecognizer)
|
|
||||||
|
|
||||||
let mentionsTapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.mentionsTap(_:)))
|
|
||||||
mentionsTapRecognizer.tapActionAtPoint = { _ in
|
|
||||||
return .waitForSingleTap
|
|
||||||
}
|
|
||||||
self.mentionsButtonTapNode.view.addGestureRecognizer(mentionsTapRecognizer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat) {
|
func update(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat) {
|
||||||
@ -121,6 +109,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
|
|
||||||
|
self.reactionsButton.updateTheme(theme: theme)
|
||||||
self.mentionsButton.updateTheme(theme: theme)
|
self.mentionsButton.updateTheme(theme: theme)
|
||||||
self.downButton.updateTheme(theme: theme)
|
self.downButton.updateTheme(theme: theme)
|
||||||
}
|
}
|
||||||
@ -154,7 +143,6 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
self.mentionsButton.isHidden = false
|
self.mentionsButton.isHidden = false
|
||||||
transition.updateAlpha(node: self.mentionsButton, alpha: 1.0)
|
transition.updateAlpha(node: self.mentionsButton, alpha: 1.0)
|
||||||
transition.updateTransformScale(node: self.mentionsButton, scale: 1.0)
|
transition.updateTransformScale(node: self.mentionsButton, scale: 1.0)
|
||||||
self.mentionsButtonTapNode.isHidden = false
|
|
||||||
} else {
|
} else {
|
||||||
transition.updateAlpha(node: self.mentionsButton, alpha: 0.0, completion: { [weak self] completed in
|
transition.updateAlpha(node: self.mentionsButton, alpha: 0.0, completion: { [weak self] completed in
|
||||||
guard let strongSelf = self, completed else {
|
guard let strongSelf = self, completed else {
|
||||||
@ -163,14 +151,12 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
strongSelf.mentionsButton.isHidden = true
|
strongSelf.mentionsButton.isHidden = true
|
||||||
})
|
})
|
||||||
transition.updateTransformScale(node: self.mentionsButton, scale: 0.2)
|
transition.updateTransformScale(node: self.mentionsButton, scale: 0.2)
|
||||||
self.mentionsButtonTapNode.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.reactionsCount != 0 {
|
if self.reactionsCount != 0 {
|
||||||
self.reactionsButton.isHidden = false
|
self.reactionsButton.isHidden = false
|
||||||
transition.updateAlpha(node: self.reactionsButton, alpha: 1.0)
|
transition.updateAlpha(node: self.reactionsButton, alpha: 1.0)
|
||||||
transition.updateTransformScale(node: self.reactionsButton, scale: 1.0)
|
transition.updateTransformScale(node: self.reactionsButton, scale: 1.0)
|
||||||
self.reactionsButtonTapNode.isHidden = false
|
|
||||||
} else {
|
} else {
|
||||||
transition.updateAlpha(node: self.reactionsButton, alpha: 0.0, completion: { [weak self] completed in
|
transition.updateAlpha(node: self.reactionsButton, alpha: 0.0, completion: { [weak self] completed in
|
||||||
guard let strongSelf = self, completed else {
|
guard let strongSelf = self, completed else {
|
||||||
@ -179,16 +165,13 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
strongSelf.reactionsButton.isHidden = true
|
strongSelf.reactionsButton.isHidden = true
|
||||||
})
|
})
|
||||||
transition.updateTransformScale(node: self.reactionsButton, scale: 0.2)
|
transition.updateTransformScale(node: self.reactionsButton, scale: 0.2)
|
||||||
self.reactionsButtonTapNode.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center)
|
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center)
|
||||||
|
|
||||||
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center)
|
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center)
|
||||||
self.mentionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize)
|
|
||||||
|
|
||||||
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center)
|
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center)
|
||||||
self.reactionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize)
|
|
||||||
|
|
||||||
return completeSize
|
return completeSize
|
||||||
}
|
}
|
||||||
@ -206,26 +189,6 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func reactionsTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
|
||||||
if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0 {
|
|
||||||
if case .tap = gesture {
|
|
||||||
self.reactionsPressed?()
|
|
||||||
} else if case .longTap = gesture {
|
|
||||||
self.reactionsMenu?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func mentionsTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
|
||||||
if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0 {
|
|
||||||
if case .tap = gesture {
|
|
||||||
self.mentionsPressed?()
|
|
||||||
} else if case .longTap = gesture {
|
|
||||||
self.mentionsMenu?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class SnapshotState {
|
final class SnapshotState {
|
||||||
fileprivate let downButtonSnapshotView: UIView?
|
fileprivate let downButtonSnapshotView: UIView?
|
||||||
|
@ -160,3 +160,37 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class ChatMessageNavigationButtonContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
|
let keepInPlace: Bool = false
|
||||||
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
let centerActionsHorizontally: Bool = true
|
||||||
|
|
||||||
|
private weak var chatNode: ChatControllerNode?
|
||||||
|
private let contentNode: ContextExtractedContentContainingNode
|
||||||
|
|
||||||
|
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||||
|
return .single(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(chatNode: ChatControllerNode, contentNode: ContextExtractedContentContainingNode) {
|
||||||
|
self.chatNode = chatNode
|
||||||
|
self.contentNode = contentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func takeView() -> ContextControllerTakeViewInfo? {
|
||||||
|
guard let chatNode = self.chatNode else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextControllerTakeViewInfo(contentContainingNode: self.contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||||
|
guard let chatNode = self.chatNode else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user