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.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)
|
||||
|
||||
if previousMessage.tags != message.tags || index != updatedIndex {
|
||||
let isNewlyAdded = previousMessage.tags.isEmpty
|
||||
if !previousMessage.tags.isEmpty {
|
||||
self.tagsTable.remove(tags: previousMessage.tags, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries)
|
||||
}
|
||||
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 {
|
||||
|
@ -133,7 +133,7 @@ class MessageHistoryTagsSummaryTable: Table {
|
||||
func removeMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) {
|
||||
if let current = self.get(key) {
|
||||
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 {
|
||||
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(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(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(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).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(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) })
|
||||
declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeMarkAllUnseenReactionsOperation.self, f: { SynchronizeMarkAllUnseenReactionsOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) })
|
||||
declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) })
|
||||
declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) })
|
||||
|
@ -3313,39 +3313,6 @@ func replayFinalState(
|
||||
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
|
||||
if updatedReactions.hasUnseen {
|
||||
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>) {
|
||||
self.queue.async {
|
||||
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> {
|
||||
guard let inputPeer = transaction.getPeer(id.peerId).flatMap(apiInputPeer) else {
|
||||
return .complete()
|
||||
}
|
||||
return network.request(Api.functions.messages.readReactions(peer: inputPeer))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .affectedHistory(pts, ptsCount, _):
|
||||
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||
if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
return network.request(Api.functions.messages.readMessageContents(id: [id.id]))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.AffectedMessages?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .affectedMessages(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
|
||||
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)
|
||||
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let peer = transaction.getPeer(id.peerId), let inputChannel = apiInputChannel(peer) {
|
||||
return network.request(Api.functions.channels.readMessageContents(channel: inputChannel, id: [id.id]))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> 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 {
|
||||
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))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
|
||||
|
||||
private final class ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper {
|
||||
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
|
||||
var result: PeerMergedOperationLogEntry?
|
||||
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!
|
||||
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
||||
} else {
|
||||
@ -62,7 +61,8 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOpera
|
||||
})
|
||||
|
||||
return f(transaction, result)
|
||||
} |> switchToLatest
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
@ -81,7 +81,7 @@ func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox,
|
||||
}
|
||||
|
||||
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 operation = entry.contents as? SynchronizeMarkAllUnseenPersonalMessagesOperation {
|
||||
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) {
|
||||
if let message = transaction.getMessage(id) {
|
||||
var consume = false
|
||||
|
@ -2,7 +2,6 @@ import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
|
||||
func addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: Transaction, peerId: PeerId, maxId: MessageId.Id) {
|
||||
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))
|
||||
}
|
||||
|
||||
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 SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
|
||||
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
||||
public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21)
|
||||
}
|
||||
|
||||
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||
|
@ -15,3 +15,19 @@ public final class SynchronizeMarkAllUnseenPersonalMessagesOperation: PostboxCod
|
||||
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
|
||||
}
|
||||
|
||||
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?) {
|
||||
for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: filterPredicate) {
|
||||
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 {
|
||||
gesture.cancel()
|
||||
return
|
||||
}
|
||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.WebSearch_RecentSectionClear, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
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
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
)))
|
||||
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.mentionsButton.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)
|
||||
}
|
||||
|
||||
self.chatDisplayNode.navigateButtons.reactionsPressed = { [weak self] in
|
||||
@ -5872,7 +5889,72 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
switch result {
|
||||
case let .result(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:
|
||||
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
|
||||
guard let strongSelf = self, strongSelf.isNodeLoaded else {
|
||||
return
|
||||
@ -12385,7 +12506,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.loadingMessage.set(.single(nil))
|
||||
self.messageIndexDisposable.set(nil)
|
||||
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 {
|
||||
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||
|
@ -732,7 +732,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.canReadHistoryValue {
|
||||
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||
strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||
} else {
|
||||
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds)
|
||||
@ -1342,7 +1342,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
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
|
||||
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
|
||||
context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||
|
@ -12,7 +12,9 @@ enum ChatHistoryNavigationButtonType {
|
||||
case reactions
|
||||
}
|
||||
|
||||
class ChatHistoryNavigationButtonNode: ASControlNode {
|
||||
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||
let containerNode: ContextExtractedContentContainingNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let imageNode: ASImageNode
|
||||
private let badgeBackgroundNode: ASImageNode
|
||||
@ -22,9 +24,9 @@ class ChatHistoryNavigationButtonNode: ASControlNode {
|
||||
didSet {
|
||||
if (oldValue != nil) != (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 {
|
||||
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) {
|
||||
self.theme = theme
|
||||
self.type = type
|
||||
|
||||
self.containerNode = ContextExtractedContentContainingNode()
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor)
|
||||
|
||||
@ -71,18 +76,30 @@ class ChatHistoryNavigationButtonNode: ASControlNode {
|
||||
self.badgeTextNode.displaysAsynchronously = false
|
||||
|
||||
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.addSubnode(self.badgeTextNode)
|
||||
self.targetNodeForActivationProgress = self.buttonNode
|
||||
|
||||
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) {
|
||||
|
@ -8,10 +8,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
|
||||
private let reactionsButton: ChatHistoryNavigationButtonNode
|
||||
private let reactionsButtonTapNode: ASDisplayNode
|
||||
private let mentionsButton: ChatHistoryNavigationButtonNode
|
||||
private let mentionsButtonTapNode: ASDisplayNode
|
||||
let reactionsButton: ChatHistoryNavigationButtonNode
|
||||
let mentionsButton: ChatHistoryNavigationButtonNode
|
||||
private let downButton: ChatHistoryNavigationButtonNode
|
||||
|
||||
var downPressed: (() -> Void)? {
|
||||
@ -21,9 +19,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
}
|
||||
|
||||
var reactionsPressed: (() -> Void)?
|
||||
var reactionsMenu: (() -> Void)?
|
||||
var mentionsPressed: (() -> Void)?
|
||||
var mentionsMenu: (() -> Void)?
|
||||
|
||||
var displayDownButton: Bool = false {
|
||||
didSet {
|
||||
@ -78,12 +74,10 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
self.mentionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .mentions)
|
||||
self.mentionsButton.alpha = 0.0
|
||||
self.mentionsButton.isHidden = true
|
||||
self.mentionsButtonTapNode = ASDisplayNode()
|
||||
|
||||
self.reactionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .reactions)
|
||||
self.reactionsButton.alpha = 0.0
|
||||
self.reactionsButton.isHidden = true
|
||||
self.reactionsButtonTapNode = ASDisplayNode()
|
||||
|
||||
self.downButton = ChatHistoryNavigationButtonNode(theme: theme, type: .down)
|
||||
self.downButton.alpha = 0.0
|
||||
@ -91,29 +85,23 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.mentionsButton.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.reactionsButton)
|
||||
self.addSubnode(self.reactionsButtonTapNode)
|
||||
self.addSubnode(self.mentionsButton)
|
||||
self.addSubnode(self.mentionsButtonTapNode)
|
||||
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() {
|
||||
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) {
|
||||
@ -121,6 +109,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
self.theme = theme
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
|
||||
self.reactionsButton.updateTheme(theme: theme)
|
||||
self.mentionsButton.updateTheme(theme: theme)
|
||||
self.downButton.updateTheme(theme: theme)
|
||||
}
|
||||
@ -154,7 +143,6 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
self.mentionsButton.isHidden = false
|
||||
transition.updateAlpha(node: self.mentionsButton, alpha: 1.0)
|
||||
transition.updateTransformScale(node: self.mentionsButton, scale: 1.0)
|
||||
self.mentionsButtonTapNode.isHidden = false
|
||||
} else {
|
||||
transition.updateAlpha(node: self.mentionsButton, alpha: 0.0, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed else {
|
||||
@ -163,14 +151,12 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
strongSelf.mentionsButton.isHidden = true
|
||||
})
|
||||
transition.updateTransformScale(node: self.mentionsButton, scale: 0.2)
|
||||
self.mentionsButtonTapNode.isHidden = true
|
||||
}
|
||||
|
||||
if self.reactionsCount != 0 {
|
||||
self.reactionsButton.isHidden = false
|
||||
transition.updateAlpha(node: self.reactionsButton, alpha: 1.0)
|
||||
transition.updateTransformScale(node: self.reactionsButton, scale: 1.0)
|
||||
self.reactionsButtonTapNode.isHidden = false
|
||||
} else {
|
||||
transition.updateAlpha(node: self.reactionsButton, alpha: 0.0, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed else {
|
||||
@ -179,16 +165,13 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
strongSelf.reactionsButton.isHidden = true
|
||||
})
|
||||
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.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)
|
||||
self.reactionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize)
|
||||
|
||||
return completeSize
|
||||
}
|
||||
@ -206,26 +189,6 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
}
|
||||
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 {
|
||||
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