Reaction updates

This commit is contained in:
Ali 2022-01-25 21:15:28 +04:00
parent f47fa686e9
commit 85b5913b6a
17 changed files with 744 additions and 102 deletions

View File

@ -7252,3 +7252,4 @@ Sorry for the inconvenience.";
"Group.Members.Other" = "OTHERS MEMBERS"; "Group.Members.Other" = "OTHERS MEMBERS";
"Conversation.ReadAllReactions" = "Read All Reactions"; "Conversation.ReadAllReactions" = "Read All Reactions";
"ChatList.UserReacted" = "Reacted %@ to your message";

View File

@ -983,6 +983,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
initialHideAuthor = true initialHideAuthor = true
messageText = psaText messageText = psaText
} }
switch itemPeer.peer {
case .user:
if let attribute = messages.first?._asMessage().reactionsAttribute {
loop: for recentPeer in attribute.recentPeers {
if recentPeer.isUnseen {
messageText = item.presentationData.strings.ChatList_UserReacted(recentPeer.value).string
break loop
}
}
}
default:
break
}
contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers) contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers)
hideAuthor = initialHideAuthor hideAuthor = initialHideAuthor
@ -1307,13 +1321,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
if !isPeerGroup { if !isPeerGroup {
if hasUnseenMentions || hasUnseenReactions { if hasUnseenMentions {
if case .archive = item.peerGroupId { if case .archive = item.peerGroupId {
currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactiveMention(item.presentationData.theme, diameter: badgeDiameter) currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactiveMention(item.presentationData.theme, diameter: badgeDiameter)
} else { } else {
currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundMention(item.presentationData.theme, diameter: badgeDiameter) currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundMention(item.presentationData.theme, diameter: badgeDiameter)
} }
mentionBadgeContent = .mention mentionBadgeContent = .mention
} else if hasUnseenReactions {
currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundReactions(item.presentationData.theme, diameter: badgeDiameter)
mentionBadgeContent = .mention
} else if item.index.pinningIndex != nil && promoInfo == nil && currentBadgeBackgroundImage == nil { } else if item.index.pinningIndex != nil && promoInfo == nil && currentBadgeBackgroundImage == nil {
currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter) currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter)
} }

View File

@ -2800,6 +2800,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
var frame = itemNode.frame var frame = itemNode.frame
frame.origin.y += offset frame.origin.y += offset
itemNode.updateFrame(frame, within: self.visibleSize) itemNode.updateFrame(frame, within: self.visibleSize)
self.didScrollWithOffset?(-offset, .immediate, itemNode)
if let accessoryItemNode = itemNode.accessoryItemNode { if let accessoryItemNode = itemNode.accessoryItemNode {
itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right)
} }

View File

@ -656,7 +656,9 @@ 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,
isLarge: false,
targetView: targetView, targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode() standaloneReactionAnimation?.removeFromSupernode()
} }
@ -863,11 +865,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
} }
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, completion: @escaping () -> Void) { 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, targetView: targetView, currentItemNode: nil, completion: completion) self.animateReactionSelection(context: context, theme: theme, reaction: reaction, isLarge: isLarge, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
} }
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, currentItemNode: ReactionNode?, completion: @escaping () -> Void) { func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, 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
@ -883,7 +885,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
} }
self.itemNode = itemNode self.itemNode = itemNode
if let targetView = targetView as? ReactionIconView { if let targetView = targetView as? ReactionIconView, !isLarge {
self.itemNodeIsEmbedded = true self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode) targetView.addSubnode(itemNode)
} else { } else {
@ -894,7 +896,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
guard let strongSelf = self, let targetView = targetView else { guard let strongSelf = self, let targetView = targetView else {
return return
} }
if let targetView = targetView as? ReactionIconView { if let targetView = targetView as? ReactionIconView, !isLarge {
strongSelf.itemNodeIsEmbedded = true strongSelf.itemNodeIsEmbedded = true
targetView.imageView.isHidden = true targetView.imageView.isHidden = true
@ -906,37 +908,54 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
itemNode.isExtracted = true itemNode.isExtracted = true
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView) let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
let expandedSize: CGSize = selfTargetRect.size var expandedSize: CGSize = selfTargetRect.size
if isLarge {
expandedSize = CGSize(width: 120.0, height: 120.0)
}
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize) let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
let effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0) let effectFrame: CGRect
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
if isLarge {
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
} else {
effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0)
}
sourceSnapshotView.frame = selfTargetRect if !self.itemNodeIsEmbedded {
self.view.addSubview(sourceSnapshotView) sourceSnapshotView.frame = selfTargetRect
sourceSnapshotView.alpha = 0.0 self.view.addSubview(sourceSnapshotView)
sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.4) sourceSnapshotView.alpha = 0.0
sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.01, completion: { [weak sourceSnapshotView] _ in sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.7)
sourceSnapshotView?.removeFromSuperview() sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.01, completion: { [weak sourceSnapshotView] _ in
}) sourceSnapshotView?.removeFromSuperview()
})
}
if self.itemNodeIsEmbedded { if self.itemNodeIsEmbedded {
itemNode.frame = targetView.bounds itemNode.frame = targetView.bounds
} else { } else {
itemNode.frame = expandedFrame itemNode.frame = expandedFrame
itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.7)
if targetView.bounds.width < 25.0 {
itemNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.15)
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} }
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: false, isPreviewing: false, transition: .immediate) itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate)
let additionalAnimationNode = AnimatedStickerNode() let additionalAnimationNode = AnimatedStickerNode()
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
let additionalAnimation: TelegramMediaFile
if isLarge {
additionalAnimation = itemNode.item.largeApplicationAnimation
if incomingMessage {
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
}
} else {
additionalAnimation = itemNode.item.applicationAnimation
}
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
additionalAnimationNode.frame = effectFrame additionalAnimationNode.frame = effectFrame
additionalAnimationNode.updateLayout(size: effectFrame.size) additionalAnimationNode.updateLayout(size: effectFrame.size)
self.addSubnode(additionalAnimationNode) self.addSubnode(additionalAnimationNode)
@ -960,28 +979,52 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
return return
} }
if let targetView = strongSelf.targetView { if isLarge {
if let targetView = targetView as? ReactionIconView { strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: true, completion: {
targetView.imageView.isHidden = false if let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
} else { let standaloneReactionAnimation = StandaloneReactionAnimation()
targetView.alpha = 1.0
targetView.isHidden = false addStandaloneReactionAnimation(standaloneReactionAnimation)
standaloneReactionAnimation.animateReactionSelection(
context: itemNode.context,
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
mainAnimationCompleted = true
intermediateCompletion()
})
} else {
if let targetView = strongSelf.targetView {
if let targetView = targetView as? ReactionIconView, !isLarge {
targetView.imageView.isHidden = false
} else {
targetView.alpha = 1.0
targetView.isHidden = false
}
} }
if strongSelf.itemNodeIsEmbedded {
strongSelf.itemNode?.removeFromSupernode()
}
mainAnimationCompleted = true
intermediateCompletion()
} }
if strongSelf.itemNodeIsEmbedded {
strongSelf.itemNode?.removeFromSupernode()
}
mainAnimationCompleted = true
intermediateCompletion()
} }
} }
additionalAnimationNode.completed = { _ in additionalAnimationNode.completed = { _ in
additionalAnimationCompleted = true additionalAnimationCompleted = true
intermediateCompletion() intermediateCompletion()
beginDismissAnimation() beginDismissAnimation()
} }
@ -992,6 +1035,58 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
}) })
} }
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
completion()
return
}
let sourceFrame = itemNode.view.convert(itemNode.bounds, to: self.view)
let targetFrame = self.view.convert(targetView.convert(targetView.bounds, to: nil), from: nil)
targetSnapshotView.frame = targetFrame
self.view.insertSubview(targetSnapshotView, belowSubview: itemNode.view)
var completedTarget = false
var targetScaleCompleted = false
let intermediateCompletion: () -> Void = {
if completedTarget && targetScaleCompleted {
completion()
}
}
let targetPosition = targetFrame.center
let duration: Double = 0.16
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
itemNode.layer.animatePosition(from: itemNode.layer.position, to: targetPosition, duration: duration, removeOnCompletion: false)
targetSnapshotView.alpha = 1.0
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
targetSnapshotView.layer.animatePosition(from: sourceFrame.center, to: targetPosition, duration: duration, removeOnCompletion: false)
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak targetSnapshotView] _ in
completedTarget = true
intermediateCompletion()
targetSnapshotView?.isHidden = true
if hideNode {
targetView.alpha = 1.0
targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.imageView.alpha = 1.0
}
targetSnapshotView?.isHidden = true
targetScaleCompleted = true
intermediateCompletion()
} else {
targetScaleCompleted = true
intermediateCompletion()
}
})
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
}
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y) self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: self, offset: -offset.y) transition.animateOffsetAdditive(node: self, offset: -offset.y)
@ -1001,7 +1096,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
self.isCancelled = true self.isCancelled = true
if let targetView = self.targetView { if let targetView = self.targetView {
if let targetView = targetView as? ReactionIconView { if let targetView = targetView as? ReactionIconView, self.itemNodeIsEmbedded {
targetView.imageView.isHidden = false targetView.imageView.isHidden = false
} else { } else {
targetView.alpha = 1.0 targetView.alpha = 1.0

View File

@ -130,8 +130,12 @@ final class ReactionNode: ASDisplayNode {
self.animationNode = animationNode self.animationNode = animationNode
self.addSubnode(animationNode) self.addSubnode(animationNode)
var didReportStarted = false
animationNode.started = { [weak self] in animationNode.started = { [weak self] in
self?.expandedAnimationDidBegin?() if !didReportStarted {
didReportStarted = true
self?.expandedAnimationDidBegin?()
}
} }
if largeExpanded { if largeExpanded {

View File

@ -170,7 +170,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
applicationAnimation: aroundAnimation, applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation largeApplicationAnimation: reaction.effectAnimation
), ),
isLarge: false,
targetView: targetView, targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode() standaloneReactionAnimation?.removeFromSupernode()
} }

View File

@ -877,7 +877,13 @@ public final class AccountViewTracker {
if !added { if !added {
attributes.append(updatedReactions) attributes.append(updatedReactions)
} }
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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) var tags = currentMessage.tags
if updatedReactions.hasUnseen {
tags.insert(.unseenReaction)
} else {
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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
}) })
default: default:
break break
@ -1167,6 +1173,23 @@ public final class AccountViewTracker {
} }
let _ = (account.postbox.transaction { transaction -> Set<MessageId> in let _ = (account.postbox.transaction { transaction -> Set<MessageId> in
let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id })) let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id }))
for id in ids {
transaction.updateMessage(id, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
for i in 0 ..< attributes.count {
if let attribute = attributes[i] as? ConsumablePersonalMentionMessageAttribute {
attributes[i] = ConsumablePersonalMentionMessageAttribute(consumed: true, pending: attribute.pending)
break
}
}
var tags = currentMessage.tags
tags.remove(.unseenPersonalMessage)
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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), summary.count > 0 { if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), summary.count > 0 {
var maxId: Int32 = summary.range.maxId var maxId: Int32 = summary.range.maxId
if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
@ -1179,9 +1202,7 @@ public final class AccountViewTracker {
return ids return ids
} }
|> deliverOn(self.queue)).start(next: { _ in |> deliverOn(self.queue)).start()
//self?.updateMarkMentionsSeenForMessageIds(messageIds: messageIds)
})
} }
} }
@ -1235,7 +1256,24 @@ public final class AccountViewTracker {
} }
let _ = (account.postbox.transaction { transaction -> Set<MessageId> in let _ = (account.postbox.transaction { transaction -> Set<MessageId> in
let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id })) let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id }))
if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud), summary.count > 0 {
for id in ids {
transaction.updateMessage(id, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
for i in 0 ..< attributes.count {
if let attribute = attributes[i] as? ReactionsMessageAttribute {
attributes[i] = attribute.withAllSeen()
break
}
}
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: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) {
var maxId: Int32 = summary.range.maxId var maxId: Int32 = summary.range.maxId
if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
maxId = index.id.id maxId = index.id.id

View File

@ -283,7 +283,33 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else { guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return .complete() return .complete()
} }
let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel)
let signal = network.request(Api.functions.messages.readReactions(peer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, Bool> in
return .fail(true)
}
|> mapToSignal { result -> Signal<Void, Bool> in
if let result = result {
switch result {
case let .affectedHistory(pts, ptsCount, offset):
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
if offset == 0 {
return .fail(true)
} else {
return .complete()
}
}
} else {
return .fail(true)
}
}
return (signal |> restart)
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
/*let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel)
let limit: Int32 = 100 let limit: Int32 = 100
let oneOperation: (Int32) -> Signal<Int32?, MTRpcError> = { maxId in let oneOperation: (Int32) -> Signal<Int32?, MTRpcError> = { maxId in
return network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1)) return network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1))
@ -365,7 +391,7 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox
return loopOperations return loopOperations
|> `catch` { _ -> Signal<Void, NoError> in |> `catch` { _ -> Signal<Void, NoError> in
return .complete() return .complete()
} }*/
} }
func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) { func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) {
@ -386,7 +412,9 @@ func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynch
break loop break loop
} }
} }
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 addSynchronizeAction { if addSynchronizeAction {

View File

@ -274,6 +274,7 @@ public enum PresentationResourceParameterKey: Hashable {
case chatListBadgeBackgroundActive(CGFloat) case chatListBadgeBackgroundActive(CGFloat)
case chatListBadgeBackgroundInactive(CGFloat) case chatListBadgeBackgroundInactive(CGFloat)
case chatListBadgeBackgroundMention(CGFloat) case chatListBadgeBackgroundMention(CGFloat)
case badgeBackgroundReactions(CGFloat)
case chatListBadgeBackgroundInactiveMention(CGFloat) case chatListBadgeBackgroundInactiveMention(CGFloat)
case chatListBadgeBackgroundPinned(CGFloat) case chatListBadgeBackgroundPinned(CGFloat)

View File

@ -683,7 +683,7 @@ public struct PresentationResourcesChat {
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/ScheduleIcon"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
} }
}) })

View File

@ -189,6 +189,12 @@ public struct PresentationResourcesChatList {
}) })
} }
public static func badgeBackgroundReactions(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.badgeBackgroundReactions(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: true, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/ReactionsBadgeIcon"), color: theme.chatList.unreadBadgeActiveTextColor))
})
}
public static func badgeBackgroundInactiveMention(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? { public static func badgeBackgroundInactiveMention(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundInactiveMention(diameter), { theme in return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundInactiveMention(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/MentionBadgeIcon"), color: theme.chatList.unreadBadgeInactiveTextColor)) return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/MentionBadgeIcon"), color: theme.chatList.unreadBadgeInactiveTextColor))

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "reactionbadge.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,156 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.447266 3.519043 cm
0.000000 0.000000 0.000000 scn
4.715820 12.213867 m
4.785645 12.213867 4.833984 12.251465 4.850098 12.326660 c
4.989746 13.110840 4.984375 13.116211 5.784668 13.261230 c
5.859863 13.282715 5.902832 13.325684 5.902832 13.390137 c
5.902832 13.465332 5.859863 13.508301 5.784668 13.524414 c
4.984375 13.685547 5.005859 13.690918 4.850098 14.458984 c
4.833984 14.534180 4.791016 14.577148 4.715820 14.577148 c
4.651367 14.577148 4.603027 14.528809 4.592285 14.464355 c
4.431152 13.680176 4.452637 13.680176 3.652344 13.524414 c
3.577148 13.508301 3.534180 13.459961 3.534180 13.390137 c
3.534180 13.331055 3.577148 13.282715 3.652344 13.261230 c
4.452637 13.105469 4.447266 13.110840 4.592285 12.321289 c
4.603027 12.256836 4.651367 12.213867 4.715820 12.213867 c
h
8.443359 10.774414 m
8.540039 10.774414 8.609863 10.838867 8.620605 10.940918 c
8.770996 12.187012 8.824707 12.219238 10.097656 12.423340 c
10.210449 12.439453 10.274902 12.498535 10.274902 12.600586 c
10.274902 12.691895 10.210449 12.756348 10.119141 12.772461 c
8.835449 13.019531 8.770996 13.008789 8.620605 14.254883 c
8.609863 14.356934 8.540039 14.421387 8.443359 14.421387 c
8.352051 14.421387 8.282227 14.356934 8.271484 14.260254 c
8.110352 12.992676 8.072754 12.955078 6.778320 12.772461 c
6.687012 12.761719 6.622559 12.691895 6.622559 12.600586 c
6.622559 12.503906 6.681641 12.439453 6.772949 12.423340 c
8.072754 12.170898 8.104980 12.176270 8.271484 10.930176 c
8.282227 10.838867 8.352051 10.774414 8.443359 10.774414 c
h
4.425781 1.772461 m
6.128418 0.069824 8.282227 0.198730 9.823730 1.734863 c
10.989258 2.900391 11.263184 4.125000 10.833496 5.591309 c
10.602539 6.536621 10.065430 7.589355 9.571289 8.513184 c
9.324219 8.980469 9.028809 9.587402 8.840820 9.802246 c
8.631348 10.038574 8.319824 10.049316 8.078125 9.839844 c
7.804199 9.598145 7.793457 9.281250 7.970703 8.770996 c
8.577637 7.100586 l
8.642090 6.939453 8.631348 6.842773 8.572266 6.783691 c
8.502441 6.713867 8.411133 6.703125 8.282227 6.832031 c
4.270020 10.844238 l
4.028320 11.085938 3.641602 11.085938 3.399902 10.844238 c
3.158203 10.602539 3.158203 10.215820 3.399902 9.974121 c
6.316406 7.057617 l
6.182129 6.993164 6.047852 6.917969 5.908203 6.826660 c
2.540527 10.194336 l
2.298828 10.436035 1.912109 10.436035 1.670410 10.194336 c
1.428711 9.952637 1.428711 9.565918 1.670410 9.324219 c
5.000488 5.994141 l
4.893066 5.870605 4.796387 5.747070 4.705078 5.618164 c
1.654297 8.668945 l
1.412598 8.910645 1.025879 8.910645 0.784180 8.668945 c
0.537109 8.427246 0.542480 8.040527 0.784180 7.798828 c
4.092773 4.484863 l
4.033691 4.312988 3.990723 4.146484 3.953125 3.985352 c
1.648926 6.289551 l
1.401855 6.536621 1.015137 6.536621 0.773438 6.294922 c
0.531738 6.047852 0.531738 5.661133 0.773438 5.419434 c
4.425781 1.772461 l
h
7.272461 9.893555 m
6.321777 10.844238 l
6.074707 11.085938 5.687988 11.085938 5.451660 10.844238 c
5.435547 10.833496 5.430176 10.822754 5.419434 10.806641 c
7.224121 8.996582 l
7.191895 9.318848 7.202637 9.619629 7.272461 9.893555 c
h
11.870117 1.734863 m
13.035645 2.900391 13.309570 4.125000 12.879883 5.591309 c
12.648926 6.536621 12.111816 7.583984 11.617676 8.513184 c
11.375977 8.980469 11.075195 9.592773 10.887207 9.796875 c
10.677734 10.038574 10.366211 10.049316 10.124512 9.839844 c
10.022461 9.753906 9.958008 9.651855 9.925781 9.544434 c
10.060059 9.286621 10.194336 9.028809 10.328613 8.770996 c
10.801270 7.874023 11.365234 6.767578 11.590820 5.763184 c
12.090332 3.990723 11.698242 2.502930 10.376953 1.187012 c
10.140625 0.950684 9.893555 0.746582 9.641113 0.574707 c
10.441406 0.676758 11.214844 1.079590 11.870117 1.734863 c
h
1.568359 0.000000 m
1.654297 0.000000 1.708008 0.053711 1.718750 0.139648 c
1.906738 1.192383 1.901367 1.213867 2.997070 1.417969 c
3.088379 1.434082 3.142090 1.482422 3.142090 1.568359 c
3.142090 1.659668 3.088379 1.708008 3.002441 1.718750 c
1.901367 1.944336 1.922852 1.960449 1.718750 3.002441 c
1.708008 3.088379 1.654297 3.136719 1.568359 3.136719 c
1.487793 3.136719 1.439453 3.088379 1.417969 3.002441 c
1.213867 1.949707 1.246094 1.933594 0.139648 1.718750 c
0.059082 1.708008 0.000000 1.659668 0.000000 1.568359 c
0.000000 1.482422 0.053711 1.434082 0.139648 1.417969 c
1.246094 1.197754 1.229980 1.187012 1.417969 0.134277 c
1.439453 0.053711 1.487793 0.000000 1.568359 0.000000 c
h
f*
n
Q
endstream
endobj
3 0 obj
4420
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 20.000000 20.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000004510 00000 n
0000004533 00000 n
0000004706 00000 n
0000004780 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4839
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "reactionbutton.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,181 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.597168 4.368164 cm
0.000000 0.000000 0.000000 scn
7.288086 18.900879 m
7.387695 18.900879 7.470703 18.958984 7.495605 19.075195 c
7.703125 20.287109 7.703125 20.295410 8.931641 20.519531 c
9.056152 20.552734 9.122559 20.619141 9.122559 20.718750 c
9.122559 20.834961 9.056152 20.901367 8.939941 20.926270 c
7.703125 21.175293 7.728027 21.183594 7.495605 22.370605 c
7.470703 22.486816 7.395996 22.553223 7.288086 22.553223 c
7.180176 22.553223 7.105469 22.478516 7.088867 22.378906 c
6.839844 21.166992 6.881348 21.166992 5.636230 20.926270 c
5.520020 20.901367 5.453613 20.826660 5.453613 20.718750 c
5.453613 20.627441 5.520020 20.552734 5.636230 20.519531 c
6.881348 20.278809 6.864746 20.287109 7.088867 19.066895 c
7.105469 18.967285 7.180176 18.900879 7.288086 18.900879 c
h
13.040527 16.676270 m
13.198242 16.676270 13.306152 16.775879 13.314453 16.933594 c
13.555176 18.859375 13.638184 18.909180 15.597168 19.224609 c
15.771484 19.249512 15.871094 19.340820 15.871094 19.498535 c
15.871094 19.639648 15.771484 19.739258 15.630371 19.764160 c
13.654785 20.145996 13.555176 20.129395 13.314453 22.055176 c
13.306152 22.212891 13.198242 22.312500 13.040527 22.312500 c
12.907715 22.312500 12.799805 22.212891 12.783203 22.063477 c
12.525879 20.104492 12.467773 20.046387 10.467285 19.764160 c
10.326172 19.747559 10.226562 19.639648 10.226562 19.498535 c
10.226562 19.349121 10.326172 19.249512 10.467285 19.224609 c
12.476074 18.834473 12.525879 18.842773 12.783203 16.916992 c
12.799805 16.775879 12.907715 16.676270 13.040527 16.676270 c
h
7.445801 17.456543 m
7.312988 17.332031 7.213379 17.190918 7.155273 17.033203 c
6.466309 17.506348 5.661133 17.423340 5.038574 16.800781 c
4.814453 16.576660 4.681641 16.294434 4.656738 16.003906 c
3.951172 16.485352 3.179199 16.427246 2.573242 15.829590 c
1.992188 15.240234 1.934082 14.443359 2.390625 13.754395 c
2.108398 13.737793 1.851074 13.613281 1.651855 13.405762 c
0.979492 12.741699 0.996094 11.820312 1.685059 11.131348 c
2.241211 10.575195 l
1.950684 10.566895 1.660156 10.425781 1.436035 10.201660 c
0.763672 9.529297 0.788574 8.566406 1.502441 7.852539 c
6.632324 2.722656 l
8.906738 0.456543 11.596191 0.000000 13.920410 1.253418 c
15.314941 1.311523 16.676270 1.934082 17.863281 3.121094 c
20.021484 5.287598 20.378418 7.960449 18.909180 11.056641 c
17.016602 15.041016 l
16.659668 15.812988 16.045410 16.261230 15.298340 16.252930 c
14.891602 16.252930 14.194336 16.037109 13.903809 15.389648 c
13.322754 15.754883 12.600586 15.705078 12.052734 15.248535 c
9.853027 17.439941 l
9.105957 18.187012 8.167969 18.170410 7.445801 17.456543 c
h
11.978027 12.260254 m
8.068359 16.161621 l
8.093262 16.252930 8.143066 16.335938 8.217773 16.410645 c
8.441895 16.634766 8.765625 16.626465 9.006348 16.385742 c
11.504883 13.887207 l
11.521484 13.737793 11.554688 13.588379 11.621094 13.438965 c
12.077637 12.334961 l
12.094238 12.301758 12.094238 12.268555 12.069336 12.251953 c
12.044434 12.218750 12.002930 12.227051 11.978027 12.260254 c
h
14.542969 3.453125 m
12.442871 1.353027 9.819824 1.411133 7.470703 3.760254 c
2.548340 8.682617 l
2.324219 8.906738 2.315918 9.205566 2.531738 9.429688 c
2.747559 9.645508 3.062988 9.645508 3.278809 9.421387 c
6.300293 6.399902 l
6.549316 6.150879 6.931152 6.175781 7.163574 6.399902 c
7.395996 6.640625 7.420898 7.022461 7.171875 7.271484 c
2.639648 11.803711 l
2.415527 12.019531 2.407227 12.326660 2.623047 12.542480 c
2.838867 12.758301 3.154297 12.758301 3.378418 12.534180 c
7.603516 8.309082 l
7.852539 8.060059 8.234375 8.084961 8.458496 8.317383 c
8.690918 8.549805 8.724121 8.931641 8.475098 9.180664 c
3.502930 14.144531 l
3.278809 14.368652 3.270508 14.684082 3.486328 14.899902 c
3.702148 15.115723 4.017578 15.107422 4.241699 14.883301 c
9.139160 9.977539 l
9.379883 9.736816 9.761719 9.753418 9.994141 9.985840 c
10.234863 10.226562 10.251465 10.608398 10.010742 10.849121 c
5.852051 15.007812 l
5.627930 15.231934 5.627930 15.539062 5.843750 15.754883 c
6.059570 15.970703 6.366699 15.970703 6.590820 15.746582 c
12.434570 9.902832 l
12.766602 9.562500 13.173340 9.587402 13.447266 9.861328 c
13.679688 10.102051 13.779297 10.417480 13.604980 10.865723 c
12.567383 13.596680 l
12.434570 13.937012 12.559082 14.210938 12.816406 14.327148 c
13.090332 14.451660 13.347656 14.327148 13.513672 13.970215 c
15.431152 9.869629 l
16.709473 7.146973 16.244629 5.154785 14.542969 3.453125 c
h
16.609863 10.434082 m
14.841797 14.177734 l
14.816895 14.252441 14.800293 14.327148 14.800293 14.410156 c
14.800293 14.650879 14.999512 14.891602 15.323242 14.891602 c
15.530762 14.891602 15.738281 14.750488 15.846191 14.509766 c
17.730469 10.492188 l
19.000488 7.777832 18.543945 5.769043 16.842285 4.083984 c
16.792480 4.034180 16.742676 3.984375 16.692871 3.942871 c
17.796875 5.818848 17.771973 7.993652 16.609863 10.434082 c
h
2.423828 0.024902 m
2.556641 0.024902 2.631348 0.107910 2.656250 0.240723 c
2.946777 1.867676 2.938477 1.900879 4.631836 2.216309 c
4.772949 2.241211 4.847656 2.315918 4.847656 2.448730 c
4.847656 2.589844 4.772949 2.664551 4.640137 2.681152 c
2.938477 3.029785 2.971680 3.054688 2.656250 4.665039 c
2.631348 4.797852 2.556641 4.872559 2.423828 4.872559 c
2.291016 4.872559 2.216309 4.797852 2.191406 4.665039 c
1.867676 3.038086 1.917480 3.013184 0.215820 2.681152 c
0.083008 2.664551 0.000000 2.589844 0.000000 2.448730 c
0.000000 2.315918 0.083008 2.241211 0.215820 2.216309 c
1.917480 1.875977 1.892578 1.859375 2.191406 0.232422 c
2.216309 0.107910 2.291016 0.024902 2.423828 0.024902 c
h
f
n
Q
endstream
endobj
3 0 obj
5599
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005689 00000 n
0000005712 00000 n
0000005885 00000 n
0000005959 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
6018
%%EOF

View File

@ -1294,7 +1294,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
applicationAnimation: aroundAnimation, applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation largeApplicationAnimation: reaction.effectAnimation
), ),
isLarge: false,
targetView: targetView, targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode() standaloneReactionAnimation?.removeFromSupernode()
} }
@ -5944,7 +5953,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
applicationAnimation: aroundAnimation, applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation largeApplicationAnimation: reaction.effectAnimation
), ),
isLarge: false,
targetView: targetView, targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode() standaloneReactionAnimation?.removeFromSupernode()
} }
@ -12505,10 +12523,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let messageId = messageLocation.messageId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { if let messageId = messageLocation.messageId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
self.loadingMessage.set(.single(nil)) self.loadingMessage.set(.single(nil))
self.messageIndexDisposable.set(nil) self.messageIndexDisposable.set(nil)
var delayCompletion = true
if self.chatDisplayNode.historyNode.isMessageVisible(id: messageId) {
delayCompletion = false
}
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition) self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
Queue.mainQueue().after(0.25, {
completion?() if delayCompletion {
}) Queue.mainQueue().after(0.25, {
completion?()
})
} else {
Queue.mainQueue().justDispatch({
completion?()
})
}
if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode { if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode)) let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))

View File

@ -1344,6 +1344,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty { if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen
let _ = strongSelf.displayUnseenReactionAnimations(messageIds: Array(messageIds))
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll() strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds) context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
} }
@ -2381,9 +2384,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in
if let strongSelf = self { if let strongSelf = self {
var newIncomingReactions: [MessageId: String] = [:] var newIncomingReactions: [MessageId: (value: String, isLarge: Bool)] = [:]
if case .peer = strongSelf.chatLocation, let previousHistoryView = strongSelf.historyView { if case .peer = strongSelf.chatLocation, let previousHistoryView = strongSelf.historyView {
var updatedIncomingReactions: [MessageId: String] = [:] var updatedIncomingReactions: [MessageId: (value: String, isLarge: Bool)] = [:]
for entry in transition.historyView.filteredEntries { for entry in transition.historyView.filteredEntries {
switch entry { switch entry {
case let .MessageEntry(message, _, _, _, _, _): case let .MessageEntry(message, _, _, _, _, _):
@ -2393,7 +2396,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if let reactions = message.reactionsAttribute { if let reactions = message.reactionsAttribute {
for recentPeer in reactions.recentPeers { for recentPeer in reactions.recentPeers {
if recentPeer.isUnseen { if recentPeer.isUnseen {
updatedIncomingReactions[message.id] = recentPeer.value updatedIncomingReactions[message.id] = (recentPeer.value, recentPeer.isLarge)
} }
} }
} }
@ -2405,7 +2408,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if let reactions = message.0.reactionsAttribute { if let reactions = message.0.reactionsAttribute {
for recentPeer in reactions.recentPeers { for recentPeer in reactions.recentPeers {
if recentPeer.isUnseen { if recentPeer.isUnseen {
updatedIncomingReactions[message.0.id] = recentPeer.value updatedIncomingReactions[message.0.id] = (recentPeer.value, recentPeer.isLarge)
} }
} }
} }
@ -2426,7 +2429,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
} }
if previousReaction != updatedReaction { if previousReaction != updatedReaction.value {
newIncomingReactions[message.id] = updatedReaction newIncomingReactions[message.id] = updatedReaction
} }
} }
@ -2441,7 +2444,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
} }
if previousReaction != updatedReaction { if previousReaction != updatedReaction.value {
newIncomingReactions[message.0.id] = updatedReaction newIncomingReactions[message.0.id] = updatedReaction
} }
} }
@ -2599,52 +2602,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
if !newIncomingReactions.isEmpty, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode { if !newIncomingReactions.isEmpty {
var visibleNewIncomingReactionMessageIds: [MessageId] = [] let messageIds = Array(newIncomingReactions.keys)
strongSelf.forEachVisibleItemNode { itemNode in
guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let updatedReaction = newIncomingReactions[item.content.firstMessage.id] else {
return
}
visibleNewIncomingReactionMessageIds.append(item.content.firstMessage.id)
if let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation()
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: strongSelf.context,
theme: item.presentationData.theme.theme,
reaction: ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
targetView: targetView,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
}
}
}
let visibleNewIncomingReactionMessageIds = strongSelf.displayUnseenReactionAnimations(messageIds: messageIds)
if !visibleNewIncomingReactionMessageIds.isEmpty { if !visibleNewIncomingReactionMessageIds.isEmpty {
strongSelf.unseenReactionsProcessingManager.add(visibleNewIncomingReactionMessageIds) strongSelf.unseenReactionsProcessingManager.add(visibleNewIncomingReactionMessageIds)
} }
@ -2696,6 +2657,79 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
private func displayUnseenReactionAnimations(messageIds: [MessageId]) -> [MessageId] {
guard let chatDisplayNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
return []
}
var visibleNewIncomingReactionMessageIds: [MessageId] = []
self.forEachVisibleItemNode { itemNode in
guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let reactionsAttribute = item.content.firstMessage.reactionsAttribute, messageIds.contains(item.content.firstMessage.id) else {
return
}
var selectedReaction: (String, Bool)?
for recentPeer in reactionsAttribute.recentPeers {
if recentPeer.isUnseen {
selectedReaction = (recentPeer.value, recentPeer.isLarge)
break
}
}
guard let (updatedReaction, updatedReactionIsLarge) = selectedReaction else {
return
}
visibleNewIncomingReactionMessageIds.append(item.content.firstMessage.id)
if let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation()
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: self.context,
theme: item.presentationData.theme.theme,
reaction: ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
guard let strongSelf = self, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
return
}
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
}
}
}
return visibleNewIncomingReactionMessageIds
}
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) {
self.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {}) self.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {})
} }
@ -2964,6 +2998,29 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
return resultMessages return resultMessages
} }
func isMessageVisible(id: MessageId) -> Bool {
var found = false
self.forEachVisibleItemNode { itemNode in
if !found, let itemNode = itemNode as? ListViewItemNode {
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
switch item.content {
case let .message(message, _, _ , _, _):
if message.id == id {
found = true
}
case let .group(messages):
for message in messages {
if message.0.id == id {
found = true
}
}
}
}
}
}
return found
}
private var selectionPanState: (selecting: Bool, initialMessageId: MessageId, toggledMessageIds: [[MessageId]])? private var selectionPanState: (selecting: Bool, initialMessageId: MessageId, toggledMessageIds: [[MessageId]])?
private var selectionScrollActivationTimer: SwiftSignalKit.Timer? private var selectionScrollActivationTimer: SwiftSignalKit.Timer?
private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator? private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator?