mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Update reactions
This commit is contained in:
parent
8edb5f967c
commit
7cd4c4e489
@ -15,6 +15,10 @@ public enum PostboxUpdateMessage {
|
|||||||
case skip
|
case skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol StoreOrUpdateMessageAction: AnyObject {
|
||||||
|
func addOrUpdate(messages: [StoreMessage], transaction: Transaction)
|
||||||
|
}
|
||||||
|
|
||||||
public final class Transaction {
|
public final class Transaction {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private weak var postbox: PostboxImpl?
|
private weak var postbox: PostboxImpl?
|
||||||
@ -507,7 +511,7 @@ public final class Transaction {
|
|||||||
|
|
||||||
public func updateMessage(_ id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
public func updateMessage(_ id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
self.postbox?.updateMessage(id, update: update)
|
self.postbox?.updateMessage(transaction: self, id: id, update: update)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32) {
|
public func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32) {
|
||||||
@ -1452,6 +1456,7 @@ final class PostboxImpl {
|
|||||||
let peerRatingTable: RatingTable<PeerId>
|
let peerRatingTable: RatingTable<PeerId>
|
||||||
|
|
||||||
var installedMessageActionsByPeerId: [PeerId: Bag<([StoreMessage], Transaction) -> Void>] = [:]
|
var installedMessageActionsByPeerId: [PeerId: Bag<([StoreMessage], Transaction) -> Void>] = [:]
|
||||||
|
var installedStoreOrUpdateMessageActionsByPeerId: [PeerId: Bag<StoreOrUpdateMessageAction>] = [:]
|
||||||
|
|
||||||
init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, tempDir: TempBoxDirectory?, useCaches: Bool) {
|
init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, tempDir: TempBoxDirectory?, useCaches: Bool) {
|
||||||
assert(queue.isCurrent())
|
assert(queue.isCurrent())
|
||||||
@ -1750,6 +1755,12 @@ final class PostboxImpl {
|
|||||||
f(peerMessages, transaction)
|
f(peerMessages, transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[peerId] {
|
||||||
|
for f in bag.copyItems() {
|
||||||
|
f.addOrUpdate(messages: peerMessages, transaction: transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return addResult
|
return addResult
|
||||||
@ -2255,11 +2266,17 @@ final class PostboxImpl {
|
|||||||
self.peerRatingTable.replace(items: peerIds)
|
self.peerRatingTable.replace(items: peerIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func updateMessage(_ id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
fileprivate func updateMessage(transaction: Transaction, id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
||||||
if let index = self.messageHistoryIndexTable.getIndex(id), let intermediateMessage = self.messageHistoryTable.getMessage(index) {
|
if let index = self.messageHistoryIndexTable.getIndex(id), let intermediateMessage = self.messageHistoryTable.getMessage(index) {
|
||||||
let message = self.renderIntermediateMessage(intermediateMessage)
|
let message = self.renderIntermediateMessage(intermediateMessage)
|
||||||
if case let .update(updatedMessage) = update(message) {
|
if case let .update(updatedMessage) = update(message) {
|
||||||
self.messageHistoryTable.updateMessage(id, message: updatedMessage, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
|
self.messageHistoryTable.updateMessage(id, message: updatedMessage, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
|
||||||
|
|
||||||
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[id.peerId] {
|
||||||
|
for f in bag.copyItems() {
|
||||||
|
f.addOrUpdate(messages: [updatedMessage], transaction: transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3397,6 +3414,24 @@ final class PostboxImpl {
|
|||||||
return disposable
|
return disposable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func installStoreOrUpdateMessageAction(peerId: PeerId, action: StoreOrUpdateMessageAction) -> Disposable {
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.queue.async {
|
||||||
|
if self.installedStoreOrUpdateMessageActionsByPeerId[peerId] == nil {
|
||||||
|
self.installedStoreOrUpdateMessageActionsByPeerId[peerId] = Bag()
|
||||||
|
}
|
||||||
|
let index = self.installedStoreOrUpdateMessageActionsByPeerId[peerId]!.add(action)
|
||||||
|
disposable.set(ActionDisposable {
|
||||||
|
self.queue.async {
|
||||||
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[peerId] {
|
||||||
|
bag.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
fileprivate func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
||||||
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
||||||
while true {
|
while true {
|
||||||
@ -4139,6 +4174,16 @@ public class Postbox {
|
|||||||
|
|
||||||
return disposable
|
return disposable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func installStoreOrUpdateMessageAction(peerId: PeerId, action: StoreOrUpdateMessageAction) -> Disposable {
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.installStoreOrUpdateMessageAction(peerId: peerId, action: action))
|
||||||
|
}
|
||||||
|
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
|
||||||
public func isMasterClient() -> Signal<Bool, NoError> {
|
public func isMasterClient() -> Signal<Bool, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
|
@ -21,6 +21,9 @@ swift_library(
|
|||||||
"//submodules/StickerResources:StickerResources",
|
"//submodules/StickerResources:StickerResources",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
|
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
|
||||||
|
"//submodules/lottie-ios:Lottie",
|
||||||
|
"//submodules/AppBundle:AppBundle",
|
||||||
|
"//submodules/AvatarNode:AvatarNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -8,6 +8,9 @@ import AccountContext
|
|||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import ReactionButtonListComponent
|
import ReactionButtonListComponent
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import Lottie
|
||||||
|
import AppBundle
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
public final class ReactionContextItem {
|
public final class ReactionContextItem {
|
||||||
public struct Reaction: Equatable {
|
public struct Reaction: Equatable {
|
||||||
@ -656,6 +659,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
context: strongSelf.context,
|
context: strongSelf.context,
|
||||||
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||||
reaction: itemNode.item,
|
reaction: itemNode.item,
|
||||||
|
avatarPeers: [],
|
||||||
isLarge: false,
|
isLarge: false,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: nil,
|
addStandaloneReactionAnimation: nil,
|
||||||
@ -859,17 +863,19 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
private weak var targetView: UIView?
|
private weak var targetView: UIView?
|
||||||
|
|
||||||
|
private var colorCallbacks: [LOTColorValueCallback] = []
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, isLarge: Bool, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
|
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, avatarPeers: [EnginePeer], isLarge: Bool, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
|
||||||
self.animateReactionSelection(context: context, theme: theme, reaction: reaction, isLarge: isLarge, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
|
self.animateReactionSelection(context: context, theme: theme, reaction: reaction, avatarPeers: avatarPeers, isLarge: isLarge, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, isLarge: Bool, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
|
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, avatarPeers: [EnginePeer], isLarge: Bool, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
|
||||||
guard let sourceSnapshotView = targetView.snapshotContentTree() else {
|
guard let sourceSnapshotView = targetView.snapshotContentTree() else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -960,6 +966,45 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
||||||
self.addSubnode(additionalAnimationNode)
|
self.addSubnode(additionalAnimationNode)
|
||||||
|
|
||||||
|
if !isLarge, let url = getAppBundle().url(forResource: "effectavatar", withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||||
|
let view = LOTAnimationView(model: composition, in: getAppBundle())
|
||||||
|
view.animationSpeed = 1.0
|
||||||
|
view.backgroundColor = nil
|
||||||
|
view.isOpaque = false
|
||||||
|
|
||||||
|
var avatarIndex = 0
|
||||||
|
|
||||||
|
let keypathIndices: [Int] = Array((1 ... 3).map({ $0 }).shuffled())
|
||||||
|
for i in keypathIndices {
|
||||||
|
var peer: EnginePeer?
|
||||||
|
if avatarIndex < avatarPeers.count {
|
||||||
|
peer = avatarPeers[avatarIndex]
|
||||||
|
}
|
||||||
|
avatarIndex += 1
|
||||||
|
|
||||||
|
if let peer = peer {
|
||||||
|
let avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 16.0))
|
||||||
|
|
||||||
|
let avatarContainer = UIView(frame: CGRect(origin: CGPoint(x: -100.0, y: -100.0), size: CGSize(width: 200.0, height: 200.0)))
|
||||||
|
|
||||||
|
avatarNode.frame = CGRect(origin: CGPoint(x: floor((200.0 - 40.0) / 2.0), y: floor((200.0 - 40.0) / 2.0)), size: CGSize(width: 40.0, height: 40.0))
|
||||||
|
avatarNode.setPeer(context: context, theme: context.sharedContext.currentPresentationData.with({ $0 }).theme, peer: peer)
|
||||||
|
avatarNode.transform = CATransform3DMakeScale(200.0 / 40.0, 200.0 / 40.0, 1.0)
|
||||||
|
avatarContainer.addSubnode(avatarNode)
|
||||||
|
|
||||||
|
view.addSubview(avatarContainer, toKeypathLayer: LOTKeypath(string: "Avatar \(i).Ellipse 1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorCallback = LOTColorValueCallback(color: UIColor.clear.cgColor)
|
||||||
|
self.colorCallbacks.append(colorCallback)
|
||||||
|
view.setValueDelegate(colorCallback, for: LOTKeypath(string: "Avatar \(i).Ellipse 1.Fill 1.Color"))
|
||||||
|
}
|
||||||
|
|
||||||
|
view.frame = additionalAnimationNode.bounds
|
||||||
|
additionalAnimationNode.view.addSubview(view)
|
||||||
|
view.play()
|
||||||
|
}
|
||||||
|
|
||||||
var mainAnimationCompleted = false
|
var mainAnimationCompleted = false
|
||||||
var additionalAnimationCompleted = false
|
var additionalAnimationCompleted = false
|
||||||
let intermediateCompletion: () -> Void = {
|
let intermediateCompletion: () -> Void = {
|
||||||
@ -990,6 +1035,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
context: itemNode.context,
|
context: itemNode.context,
|
||||||
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||||
reaction: itemNode.item,
|
reaction: itemNode.item,
|
||||||
|
avatarPeers: avatarPeers,
|
||||||
isLarge: false,
|
isLarge: false,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: nil,
|
addStandaloneReactionAnimation: nil,
|
||||||
@ -1119,7 +1165,7 @@ public final class StandaloneDismissReactionAnimation: ASDisplayNode {
|
|||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateReactionDismiss(sourceView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
public func animateReactionDismiss(sourceView: UIView, hideNode: Bool, isIncoming: Bool, completion: @escaping () -> Void) {
|
||||||
guard let sourceSnapshotView = sourceView.snapshotContentTree() else {
|
guard let sourceSnapshotView = sourceView.snapshotContentTree() else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -1133,7 +1179,7 @@ public final class StandaloneDismissReactionAnimation: ASDisplayNode {
|
|||||||
self.view.addSubview(sourceSnapshotView)
|
self.view.addSubview(sourceSnapshotView)
|
||||||
|
|
||||||
var targetOffset: CGFloat = 120.0
|
var targetOffset: CGFloat = 120.0
|
||||||
if sourceRect.midX > self.bounds.width / 2.0 {
|
if !isIncoming {
|
||||||
targetOffset = -targetOffset
|
targetOffset = -targetOffset
|
||||||
}
|
}
|
||||||
let targetPoint = CGPoint(x: sourceRect.midX + targetOffset, y: sourceRect.midY)
|
let targetPoint = CGPoint(x: sourceRect.midX + targetOffset, y: sourceRect.midY)
|
||||||
|
@ -170,6 +170,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
|||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
),
|
),
|
||||||
|
avatarPeers: [],
|
||||||
isLarge: false,
|
isLarge: false,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: nil,
|
addStandaloneReactionAnimation: nil,
|
||||||
|
@ -3,7 +3,6 @@ import Postbox
|
|||||||
import TelegramApi
|
import TelegramApi
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
|
||||||
func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable {
|
func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable {
|
||||||
return postbox.installStoreMessageAction(peerId: peerId, { messages, transaction in
|
return postbox.installStoreMessageAction(peerId: peerId, { messages, transaction in
|
||||||
var consumeMessageIds: [MessageId] = []
|
var consumeMessageIds: [MessageId] = []
|
||||||
@ -17,7 +16,7 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag
|
|||||||
var hasUnconsumedContent = false
|
var hasUnconsumedContent = false
|
||||||
var hasUnseenReactions = false
|
var hasUnseenReactions = false
|
||||||
|
|
||||||
if message.tags.contains(.unseenPersonalMessage) {
|
if message.tags.contains(.unseenPersonalMessage) || message.tags.contains(.unseenReaction) {
|
||||||
inner: for attribute in message.attributes {
|
inner: for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.consumed, !attribute.pending {
|
if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.consumed, !attribute.pending {
|
||||||
hasUnconsumedMention = true
|
hasUnconsumedMention = true
|
||||||
@ -65,7 +64,9 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
var tags = currentMessage.tags
|
||||||
|
tags.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: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
})
|
})
|
||||||
|
|
||||||
if consumeMessageIds.contains(id) {
|
if consumeMessageIds.contains(id) {
|
||||||
@ -81,3 +82,82 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct VisibleMessageRange {
|
||||||
|
public var lowerBound: MessageIndex
|
||||||
|
public var upperBound: MessageIndex?
|
||||||
|
|
||||||
|
public init(lowerBound: MessageIndex, upperBound: MessageIndex?) {
|
||||||
|
self.lowerBound = lowerBound
|
||||||
|
self.upperBound = upperBound
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func contains(index: MessageIndex) -> Bool {
|
||||||
|
if index < lowerBound {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let upperBound = self.upperBound {
|
||||||
|
if index > upperBound {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StoreOrUpdateMessageActionImpl: StoreOrUpdateMessageAction {
|
||||||
|
private let getVisibleRange: () -> VisibleMessageRange?
|
||||||
|
private let didReadReactionsInMessages: ([MessageId: [ReactionsMessageAttribute.RecentPeer]]) -> Void
|
||||||
|
|
||||||
|
init(getVisibleRange: @escaping () -> VisibleMessageRange?, didReadReactionsInMessages: @escaping ([MessageId: [ReactionsMessageAttribute.RecentPeer]]) -> Void) {
|
||||||
|
self.getVisibleRange = getVisibleRange
|
||||||
|
self.didReadReactionsInMessages = didReadReactionsInMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
func addOrUpdate(messages: [StoreMessage], transaction: Transaction) {
|
||||||
|
var readReactionIds: [MessageId: [ReactionsMessageAttribute.RecentPeer]] = [:]
|
||||||
|
|
||||||
|
guard let visibleRange = self.getVisibleRange() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
guard let index = message.index else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !visibleRange.contains(index: index) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.tags.contains(.unseenReaction) {
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen {
|
||||||
|
readReactionIds[index.id] = attribute.recentPeers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in readReactionIds.keys {
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
reactionsLoop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
||||||
|
attributes[j] = attribute.withAllSeen()
|
||||||
|
break reactionsLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tags = currentMessage.tags
|
||||||
|
tags.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: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
transaction.setPendingMessageAction(type: .readReaction, id: id, action: ReadReactionAction())
|
||||||
|
}
|
||||||
|
|
||||||
|
self.didReadReactionsInMessages(readReactionIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_installInteractiveReadReactionsAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, getVisibleRange: @escaping () -> VisibleMessageRange?, didReadReactionsInMessages: @escaping ([MessageId: [ReactionsMessageAttribute.RecentPeer]]) -> Void) -> Disposable {
|
||||||
|
return postbox.installStoreOrUpdateMessageAction(peerId: peerId, action: StoreOrUpdateMessageActionImpl(getVisibleRange: getVisibleRange, didReadReactionsInMessages: didReadReactionsInMessages))
|
||||||
|
}
|
||||||
|
@ -131,6 +131,10 @@ public extension TelegramEngine {
|
|||||||
public func installInteractiveReadMessagesAction(peerId: PeerId) -> Disposable {
|
public func installInteractiveReadMessagesAction(peerId: PeerId) -> Disposable {
|
||||||
return _internal_installInteractiveReadMessagesAction(postbox: self.account.postbox, stateManager: self.account.stateManager, peerId: peerId)
|
return _internal_installInteractiveReadMessagesAction(postbox: self.account.postbox, stateManager: self.account.stateManager, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func installInteractiveReadReactionsAction(peerId: PeerId, getVisibleRange: @escaping () -> VisibleMessageRange?, didReadReactionsInMessages: @escaping ([MessageId: [ReactionsMessageAttribute.RecentPeer]]) -> Void) -> Disposable {
|
||||||
|
return _internal_installInteractiveReadReactionsAction(postbox: self.account.postbox, stateManager: self.account.stateManager, peerId: peerId, getVisibleRange: getVisibleRange, didReadReactionsInMessages: didReadReactionsInMessages)
|
||||||
|
}
|
||||||
|
|
||||||
public func requestMessageSelectPollOption(messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal<TelegramMediaPoll?, RequestMessageSelectPollOptionError> {
|
public func requestMessageSelectPollOption(messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal<TelegramMediaPoll?, RequestMessageSelectPollOptionError> {
|
||||||
return _internal_requestMessageSelectPollOption(account: self.account, messageId: messageId, opaqueIdentifiers: opaqueIdentifiers)
|
return _internal_requestMessageSelectPollOption(account: self.account, messageId: messageId, opaqueIdentifiers: opaqueIdentifiers)
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1294,6 +1294,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
),
|
),
|
||||||
|
avatarPeers: [],
|
||||||
isLarge: false,
|
isLarge: false,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
||||||
@ -1331,7 +1332,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let standaloneDismissAnimation = StandaloneDismissReactionAnimation()
|
let standaloneDismissAnimation = StandaloneDismissReactionAnimation()
|
||||||
standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds
|
standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds
|
||||||
strongSelf.chatDisplayNode.addSubnode(standaloneDismissAnimation)
|
strongSelf.chatDisplayNode.addSubnode(standaloneDismissAnimation)
|
||||||
standaloneDismissAnimation.animateReactionDismiss(sourceView: targetView, hideNode: hideRemovedReaction, completion: { [weak standaloneDismissAnimation] in
|
standaloneDismissAnimation.animateReactionDismiss(sourceView: targetView, hideNode: hideRemovedReaction, isIncoming: message.effectivelyIncoming(strongSelf.context.account.peerId), completion: { [weak standaloneDismissAnimation] in
|
||||||
standaloneDismissAnimation?.removeFromSupernode()
|
standaloneDismissAnimation?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4682,7 +4683,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
)
|
)
|
||||||
|> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
|
|> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
|
||||||
let topVisibleMessage: MessageId?
|
let topVisibleMessage: MessageId?
|
||||||
topVisibleMessage = topVisibleMessageRange?.upperBound
|
topVisibleMessage = topVisibleMessageRange?.upperBound.id
|
||||||
|
|
||||||
if let scrolledToMessageId = scrolledToMessageId {
|
if let scrolledToMessageId = scrolledToMessageId {
|
||||||
if let topVisibleMessage = topVisibleMessage {
|
if let topVisibleMessage = topVisibleMessage {
|
||||||
@ -5909,20 +5910,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard item.message.id == messageId else {
|
guard item.message.id == messageId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var maybeUpdatedReaction: String?
|
var maybeUpdatedReaction: (String, Bool, EnginePeer?)?
|
||||||
if let attribute = item.message.reactionsAttribute {
|
if let attribute = item.message.reactionsAttribute {
|
||||||
for recentPeer in attribute.recentPeers {
|
for recentPeer in attribute.recentPeers {
|
||||||
if recentPeer.isUnseen {
|
if recentPeer.isUnseen {
|
||||||
maybeUpdatedReaction = recentPeer.value
|
maybeUpdatedReaction = (recentPeer.value, recentPeer.isLarge, item.message.peers[recentPeer.peerId].flatMap(EnginePeer.init))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let updatedReaction = maybeUpdatedReaction else {
|
guard let (updatedReaction, updatedReactionIsLarge, updatedReactionPeer) = maybeUpdatedReaction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var avatarPeers: [EnginePeer] = []
|
||||||
|
if let updatedReactionPeer = updatedReactionPeer {
|
||||||
|
avatarPeers.append(updatedReactionPeer)
|
||||||
|
}
|
||||||
|
|
||||||
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
|
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -5953,7 +5959,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
),
|
),
|
||||||
isLarge: false,
|
avatarPeers: avatarPeers,
|
||||||
|
isLarge: updatedReactionIsLarge,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
@ -27,8 +27,8 @@ extension ChatReplyThreadMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ChatTopVisibleMessageRange: Equatable {
|
struct ChatTopVisibleMessageRange: Equatable {
|
||||||
var lowerBound: MessageId
|
var lowerBound: MessageIndex
|
||||||
var upperBound: MessageId
|
var upperBound: MessageIndex
|
||||||
var isLast: Bool
|
var isLast: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,6 +549,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
public var isScrollAtBottomPositionUpdated: (() -> Void)?
|
public var isScrollAtBottomPositionUpdated: (() -> Void)?
|
||||||
|
|
||||||
private var interactiveReadActionDisposable: Disposable?
|
private var interactiveReadActionDisposable: Disposable?
|
||||||
|
private var interactiveReadReactionsDisposable: Disposable?
|
||||||
|
private var displayUnseenReactionAnimationsTimestamps: [MessageId: Double] = [:]
|
||||||
|
|
||||||
public var contentPositionChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
public var contentPositionChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||||
|
|
||||||
@ -578,23 +580,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
private let preloadAdPeerDisposable = MetaDisposable()
|
private let preloadAdPeerDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var refreshDisplayedItemRangeTimer: SwiftSignalKit.Timer?
|
private var refreshDisplayedItemRangeTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
/*var historyScrollingArea: SparseDiscreteScrollingArea? {
|
private var visibleMessageRange = Atomic<VisibleMessageRange?>(value: nil)
|
||||||
didSet {
|
|
||||||
oldValue?.navigateToPosition = nil
|
|
||||||
if let historyScrollingArea = self.historyScrollingArea {
|
|
||||||
historyScrollingArea.navigateToPosition = { [weak self] position in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.navigateToAbsolutePosition(position: position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
//private var scrollingState: ListView.ScrollingIndicatorState?
|
|
||||||
//private let sparseScrollingContext: SparseMessageScrollingContext?
|
|
||||||
//private let scrollNavigationDisposable = MetaDisposable()
|
|
||||||
|
|
||||||
private let clientId: Atomic<Int32>
|
private let clientId: Atomic<Int32>
|
||||||
|
|
||||||
@ -1554,6 +1541,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
self.historyDisposable.dispose()
|
self.historyDisposable.dispose()
|
||||||
self.readHistoryDisposable.dispose()
|
self.readHistoryDisposable.dispose()
|
||||||
self.interactiveReadActionDisposable?.dispose()
|
self.interactiveReadActionDisposable?.dispose()
|
||||||
|
self.interactiveReadReactionsDisposable?.dispose()
|
||||||
self.canReadHistoryDisposable?.dispose()
|
self.canReadHistoryDisposable?.dispose()
|
||||||
self.loadedMessagesFromCachedDataDisposable?.dispose()
|
self.loadedMessagesFromCachedDataDisposable?.dispose()
|
||||||
self.preloadAdPeerDisposable.dispose()
|
self.preloadAdPeerDisposable.dispose()
|
||||||
@ -1904,9 +1892,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
isTopReplyThreadMessageShownValue = true
|
isTopReplyThreadMessageShownValue = true
|
||||||
}
|
}
|
||||||
if let topVisibleMessageRangeValue = topVisibleMessageRange {
|
if let topVisibleMessageRangeValue = topVisibleMessageRange {
|
||||||
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: topVisibleMessageRangeValue.lowerBound, upperBound: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: topVisibleMessageRangeValue.lowerBound, upperBound: message.index, isLast: i == historyView.filteredEntries.count - 1)
|
||||||
} else {
|
} else {
|
||||||
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: message.id, upperBound: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: message.index, upperBound: message.index, isLast: i == historyView.filteredEntries.count - 1)
|
||||||
}
|
}
|
||||||
case let .MessageGroupEntry(_, messages, _):
|
case let .MessageGroupEntry(_, messages, _):
|
||||||
for (message, _, _, _, _) in messages {
|
for (message, _, _, _, _) in messages {
|
||||||
@ -1945,9 +1933,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
isTopReplyThreadMessageShownValue = true
|
isTopReplyThreadMessageShownValue = true
|
||||||
}
|
}
|
||||||
if let topVisibleMessageRangeValue = topVisibleMessageRange {
|
if let topVisibleMessageRangeValue = topVisibleMessageRange {
|
||||||
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: topVisibleMessageRangeValue.lowerBound, upperBound: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: topVisibleMessageRangeValue.lowerBound, upperBound: message.index, isLast: i == historyView.filteredEntries.count - 1)
|
||||||
} else {
|
} else {
|
||||||
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: message.id, upperBound: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: message.index, upperBound: message.index, isLast: i == historyView.filteredEntries.count - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -2076,6 +2064,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
if !messageIdsWithUnseenReactions.isEmpty {
|
if !messageIdsWithUnseenReactions.isEmpty {
|
||||||
self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions)
|
self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions)
|
||||||
|
|
||||||
|
if self.canReadHistoryValue && !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||||
|
let _ = self.displayUnseenReactionAnimations(messageIds: messageIdsWithUnseenReactions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !messageIdsWithPossibleReactions.isEmpty {
|
if !messageIdsWithPossibleReactions.isEmpty {
|
||||||
self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions)
|
self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions)
|
||||||
@ -2112,6 +2104,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
|
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
|
||||||
self.topVisibleMessageRange.set(topVisibleMessageRange)
|
self.topVisibleMessageRange.set(topVisibleMessageRange)
|
||||||
|
let _ = self.visibleMessageRange.swap(topVisibleMessageRange.flatMap { range in
|
||||||
|
return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound)
|
||||||
|
})
|
||||||
|
|
||||||
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
|
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
|
||||||
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
|
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
|
||||||
@ -2657,7 +2652,21 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func displayUnseenReactionAnimations(messageIds: [MessageId]) -> [MessageId] {
|
private func displayUnseenReactionAnimations(messageIds: [MessageId], forceMapping: [MessageId: [ReactionsMessageAttribute.RecentPeer]] = [:]) -> [MessageId] {
|
||||||
|
let timestamp = CACurrentMediaTime()
|
||||||
|
var messageIds = messageIds
|
||||||
|
for i in (0 ..< messageIds.count).reversed() {
|
||||||
|
if let previousTimestamp = self.displayUnseenReactionAnimationsTimestamps[messageIds[i]], previousTimestamp + 1.0 > timestamp {
|
||||||
|
messageIds.remove(at: i)
|
||||||
|
} else {
|
||||||
|
self.displayUnseenReactionAnimationsTimestamps[messageIds[i]] = timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if messageIds.isEmpty {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
guard let chatDisplayNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
|
guard let chatDisplayNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -2667,15 +2676,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedReaction: (String, Bool)?
|
var selectedReaction: (String, EnginePeer?, Bool)?
|
||||||
for recentPeer in reactionsAttribute.recentPeers {
|
let recentPeers = forceMapping[item.content.firstMessage.id] ?? reactionsAttribute.recentPeers
|
||||||
|
for recentPeer in recentPeers {
|
||||||
if recentPeer.isUnseen {
|
if recentPeer.isUnseen {
|
||||||
selectedReaction = (recentPeer.value, recentPeer.isLarge)
|
selectedReaction = (recentPeer.value, item.content.firstMessage.peers[recentPeer.peerId].flatMap(EnginePeer.init), recentPeer.isLarge)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let (updatedReaction, updatedReactionIsLarge) = selectedReaction else {
|
guard let (updatedReaction, updateReactionPeer, updatedReactionIsLarge) = selectedReaction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2709,6 +2719,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
),
|
),
|
||||||
|
avatarPeers: updateReactionPeer.flatMap({ [$0] }) ?? [],
|
||||||
isLarge: updatedReactionIsLarge,
|
isLarge: updatedReactionIsLarge,
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||||
@ -2755,6 +2766,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
|
|
||||||
private func updateReadHistoryActions() {
|
private func updateReadHistoryActions() {
|
||||||
let canRead = self.canReadHistoryValue && self.isScrollAtBottomPosition
|
let canRead = self.canReadHistoryValue && self.isScrollAtBottomPosition
|
||||||
|
|
||||||
if canRead != (self.interactiveReadActionDisposable != nil) {
|
if canRead != (self.interactiveReadActionDisposable != nil) {
|
||||||
if let interactiveReadActionDisposable = self.interactiveReadActionDisposable {
|
if let interactiveReadActionDisposable = self.interactiveReadActionDisposable {
|
||||||
if !canRead {
|
if !canRead {
|
||||||
@ -2769,6 +2781,31 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if canRead != (self.interactiveReadReactionsDisposable != nil) {
|
||||||
|
if let interactiveReadReactionsDisposable = self.interactiveReadReactionsDisposable {
|
||||||
|
if !canRead {
|
||||||
|
interactiveReadReactionsDisposable.dispose()
|
||||||
|
self.interactiveReadReactionsDisposable = nil
|
||||||
|
}
|
||||||
|
} else if self.interactiveReadReactionsDisposable == nil {
|
||||||
|
if case let .peer(peerId) = self.chatLocation {
|
||||||
|
if !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||||
|
let visibleMessageRange = self.visibleMessageRange
|
||||||
|
self.interactiveReadReactionsDisposable = context.engine.messages.installInteractiveReadReactionsAction(peerId: peerId, getVisibleRange: {
|
||||||
|
return visibleMessageRange.with { $0 }
|
||||||
|
}, didReadReactionsInMessages: { [weak self] idsAndReactions in
|
||||||
|
Queue.mainQueue().after(0.2, {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = strongSelf.displayUnseenReactionAnimations(messageIds: Array(idsAndReactions.keys), forceMapping: idsAndReactions)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lastVisbleMesssage() -> Message? {
|
func lastVisbleMesssage() -> Message? {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user