Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-01-28 01:34:23 +03:00
commit 72c2d6d509
9 changed files with 268 additions and 44 deletions

View File

@ -15,6 +15,10 @@ public enum PostboxUpdateMessage {
case skip
}
public protocol StoreOrUpdateMessageAction: AnyObject {
func addOrUpdate(messages: [StoreMessage], transaction: Transaction)
}
public final class Transaction {
private let queue: Queue
private weak var postbox: PostboxImpl?
@ -507,7 +511,7 @@ public final class Transaction {
public func updateMessage(_ id: MessageId, update: (Message) -> PostboxUpdateMessage) {
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) {
@ -1452,6 +1456,7 @@ final class PostboxImpl {
let peerRatingTable: RatingTable<PeerId>
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) {
assert(queue.isCurrent())
@ -1750,6 +1755,12 @@ final class PostboxImpl {
f(peerMessages, transaction)
}
}
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[peerId] {
for f in bag.copyItems() {
f.addOrUpdate(messages: peerMessages, transaction: transaction)
}
}
}
return addResult
@ -2255,11 +2266,17 @@ final class PostboxImpl {
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) {
let message = self.renderIntermediateMessage(intermediateMessage)
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)
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
}
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) {
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
while true {
@ -4139,6 +4174,16 @@ public class Postbox {
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> {
return Signal { subscriber in

View File

@ -21,6 +21,9 @@ swift_library(
"//submodules/StickerResources:StickerResources",
"//submodules/AccountContext:AccountContext",
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
"//submodules/lottie-ios:Lottie",
"//submodules/AppBundle:AppBundle",
"//submodules/AvatarNode:AvatarNode",
],
visibility = [
"//visibility:public",

View File

@ -8,6 +8,9 @@ import AccountContext
import TelegramAnimatedStickerNode
import ReactionButtonListComponent
import SwiftSignalKit
import Lottie
import AppBundle
import AvatarNode
public final class ReactionContextItem {
public struct Reaction: Equatable {
@ -656,6 +659,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
context: strongSelf.context,
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
avatarPeers: [],
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
@ -859,17 +863,19 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
private weak var targetView: UIView?
private var colorCallbacks: [LOTColorValueCallback] = []
override public init() {
super.init()
self.isUserInteractionEnabled = false
}
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, 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)
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, 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 {
completion()
return
@ -960,6 +966,45 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
additionalAnimationNode.updateLayout(size: effectFrame.size)
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 additionalAnimationCompleted = false
let intermediateCompletion: () -> Void = {
@ -990,6 +1035,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
context: itemNode.context,
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
avatarPeers: avatarPeers,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
@ -1119,7 +1165,7 @@ public final class StandaloneDismissReactionAnimation: ASDisplayNode {
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 {
completion()
return
@ -1133,7 +1179,7 @@ public final class StandaloneDismissReactionAnimation: ASDisplayNode {
self.view.addSubview(sourceSnapshotView)
var targetOffset: CGFloat = 120.0
if sourceRect.midX > self.bounds.width / 2.0 {
if !isIncoming {
targetOffset = -targetOffset
}
let targetPoint = CGPoint(x: sourceRect.midX + targetOffset, y: sourceRect.midY)

View File

@ -170,6 +170,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
avatarPeers: [],
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,

View File

@ -3,7 +3,6 @@ import Postbox
import TelegramApi
import SwiftSignalKit
func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable {
return postbox.installStoreMessageAction(peerId: peerId, { messages, transaction in
var consumeMessageIds: [MessageId] = []
@ -17,7 +16,7 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag
var hasUnconsumedContent = false
var hasUnseenReactions = false
if message.tags.contains(.unseenPersonalMessage) {
if message.tags.contains(.unseenPersonalMessage) || message.tags.contains(.unseenReaction) {
inner: for attribute in message.attributes {
if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.consumed, !attribute.pending {
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) {
@ -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))
}

View File

@ -131,6 +131,10 @@ public extension TelegramEngine {
public func installInteractiveReadMessagesAction(peerId: PeerId) -> Disposable {
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> {
return _internal_requestMessageSelectPollOption(account: self.account, messageId: messageId, opaqueIdentifiers: opaqueIdentifiers)

File diff suppressed because one or more lines are too long

View File

@ -1294,6 +1294,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
avatarPeers: [],
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
@ -1331,7 +1332,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let standaloneDismissAnimation = StandaloneDismissReactionAnimation()
standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds
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()
})
}
@ -4682,7 +4683,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
)
|> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
let topVisibleMessage: MessageId?
topVisibleMessage = topVisibleMessageRange?.upperBound
topVisibleMessage = topVisibleMessageRange?.upperBound.id
if let scrolledToMessageId = scrolledToMessageId {
if let topVisibleMessage = topVisibleMessage {
@ -5909,20 +5910,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard item.message.id == messageId else {
return
}
var maybeUpdatedReaction: String?
var maybeUpdatedReaction: (String, Bool, EnginePeer?)?
if let attribute = item.message.reactionsAttribute {
for recentPeer in attribute.recentPeers {
if recentPeer.isUnseen {
maybeUpdatedReaction = recentPeer.value
maybeUpdatedReaction = (recentPeer.value, recentPeer.isLarge, item.message.peers[recentPeer.peerId].flatMap(EnginePeer.init))
break
}
}
}
guard let updatedReaction = maybeUpdatedReaction else {
guard let (updatedReaction, updatedReactionIsLarge, updatedReactionPeer) = maybeUpdatedReaction else {
return
}
var avatarPeers: [EnginePeer] = []
if let updatedReactionPeer = updatedReactionPeer {
avatarPeers.append(updatedReactionPeer)
}
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
return
}
@ -5953,7 +5959,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
isLarge: false,
avatarPeers: avatarPeers,
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {

View File

@ -27,8 +27,8 @@ extension ChatReplyThreadMessage {
}
struct ChatTopVisibleMessageRange: Equatable {
var lowerBound: MessageId
var upperBound: MessageId
var lowerBound: MessageIndex
var upperBound: MessageIndex
var isLast: Bool
}
@ -549,6 +549,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
public var isScrollAtBottomPositionUpdated: (() -> Void)?
private var interactiveReadActionDisposable: Disposable?
private var interactiveReadReactionsDisposable: Disposable?
private var displayUnseenReactionAnimationsTimestamps: [MessageId: Double] = [:]
public var contentPositionChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
@ -578,23 +580,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private let preloadAdPeerDisposable = MetaDisposable()
private var refreshDisplayedItemRangeTimer: SwiftSignalKit.Timer?
/*var historyScrollingArea: SparseDiscreteScrollingArea? {
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 var visibleMessageRange = Atomic<VisibleMessageRange?>(value: nil)
private let clientId: Atomic<Int32>
@ -1554,6 +1541,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.historyDisposable.dispose()
self.readHistoryDisposable.dispose()
self.interactiveReadActionDisposable?.dispose()
self.interactiveReadReactionsDisposable?.dispose()
self.canReadHistoryDisposable?.dispose()
self.loadedMessagesFromCachedDataDisposable?.dispose()
self.preloadAdPeerDisposable.dispose()
@ -1904,9 +1892,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
isTopReplyThreadMessageShownValue = true
}
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 {
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, _):
for (message, _, _, _, _) in messages {
@ -1945,9 +1933,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
isTopReplyThreadMessageShownValue = true
}
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 {
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:
@ -2076,6 +2064,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
if !messageIdsWithUnseenReactions.isEmpty {
self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions)
if self.canReadHistoryValue && !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
let _ = self.displayUnseenReactionAnimations(messageIds: messageIdsWithUnseenReactions)
}
}
if !messageIdsWithPossibleReactions.isEmpty {
self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions)
@ -2112,6 +2104,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
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 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 {
return []
}
@ -2667,15 +2676,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
return
}
var selectedReaction: (String, Bool)?
for recentPeer in reactionsAttribute.recentPeers {
var selectedReaction: (String, EnginePeer?, Bool)?
let recentPeers = forceMapping[item.content.firstMessage.id] ?? reactionsAttribute.recentPeers
for recentPeer in recentPeers {
if recentPeer.isUnseen {
selectedReaction = (recentPeer.value, recentPeer.isLarge)
selectedReaction = (recentPeer.value, item.content.firstMessage.peers[recentPeer.peerId].flatMap(EnginePeer.init), recentPeer.isLarge)
break
}
}
guard let (updatedReaction, updatedReactionIsLarge) = selectedReaction else {
guard let (updatedReaction, updateReactionPeer, updatedReactionIsLarge) = selectedReaction else {
return
}
@ -2709,6 +2719,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
avatarPeers: updateReactionPeer.flatMap({ [$0] }) ?? [],
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
@ -2755,6 +2766,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private func updateReadHistoryActions() {
let canRead = self.canReadHistoryValue && self.isScrollAtBottomPosition
if canRead != (self.interactiveReadActionDisposable != nil) {
if let interactiveReadActionDisposable = self.interactiveReadActionDisposable {
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? {