mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
72c2d6d509
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -170,6 +170,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation
|
||||
),
|
||||
avatarPeers: [],
|
||||
isLarge: false,
|
||||
targetView: targetView,
|
||||
addStandaloneReactionAnimation: nil,
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
@ -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 {
|
||||
|
@ -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? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user