diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d616c53d36..382610468c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7252,3 +7252,4 @@ Sorry for the inconvenience."; "Group.Members.Other" = "OTHERS MEMBERS"; "Conversation.ReadAllReactions" = "Read All Reactions"; +"ChatList.UserReacted" = "Reacted %@ to your message"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index bce85e8aea..39512b026b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -983,6 +983,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { initialHideAuthor = true 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) hideAuthor = initialHideAuthor @@ -1307,13 +1321,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if !isPeerGroup { - if hasUnseenMentions || hasUnseenReactions { + if hasUnseenMentions { if case .archive = item.peerGroupId { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactiveMention(item.presentationData.theme, diameter: badgeDiameter) } else { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundMention(item.presentationData.theme, diameter: badgeDiameter) } 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 { currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter) } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index ea6d8cff4c..5a668d71df 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -2800,6 +2800,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var frame = itemNode.frame frame.origin.y += offset itemNode.updateFrame(frame, within: self.visibleSize) + self.didScrollWithOffset?(-offset, .immediate, itemNode) if let accessoryItemNode = itemNode.accessoryItemNode { itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index ccbde6eab7..caf93b607c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -656,7 +656,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { context: strongSelf.context, theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme, reaction: itemNode.item, + isLarge: false, targetView: targetView, + addStandaloneReactionAnimation: nil, completion: { [weak standaloneReactionAnimation] in standaloneReactionAnimation?.removeFromSupernode() } @@ -863,11 +865,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.isUserInteractionEnabled = false } - public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, completion: @escaping () -> Void) { - self.animateReactionSelection(context: context, theme: theme, reaction: reaction, targetView: targetView, currentItemNode: nil, completion: completion) + 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) } - 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 { completion() return @@ -883,7 +885,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } self.itemNode = itemNode - if let targetView = targetView as? ReactionIconView { + if let targetView = targetView as? ReactionIconView, !isLarge { self.itemNodeIsEmbedded = true targetView.addSubnode(itemNode) } else { @@ -894,7 +896,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { guard let strongSelf = self, let targetView = targetView else { return } - if let targetView = targetView as? ReactionIconView { + if let targetView = targetView as? ReactionIconView, !isLarge { strongSelf.itemNodeIsEmbedded = true targetView.imageView.isHidden = true @@ -906,37 +908,54 @@ public final class StandaloneReactionAnimation: ASDisplayNode { itemNode.isExtracted = true 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 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 - self.view.addSubview(sourceSnapshotView) - sourceSnapshotView.alpha = 0.0 - sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.4) - sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.01, completion: { [weak sourceSnapshotView] _ in - sourceSnapshotView?.removeFromSuperview() - }) + if !self.itemNodeIsEmbedded { + sourceSnapshotView.frame = selfTargetRect + self.view.addSubview(sourceSnapshotView) + sourceSnapshotView.alpha = 0.0 + sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.7) + sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.01, completion: { [weak sourceSnapshotView] _ in + sourceSnapshotView?.removeFromSuperview() + }) + } if self.itemNodeIsEmbedded { itemNode.frame = targetView.bounds } else { itemNode.frame = expandedFrame - itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) - - 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.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.7) } - 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() - 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.updateLayout(size: effectFrame.size) self.addSubnode(additionalAnimationNode) @@ -960,28 +979,52 @@ public final class StandaloneReactionAnimation: ASDisplayNode { return } - if let targetView = strongSelf.targetView { - if let targetView = targetView as? ReactionIconView { - targetView.imageView.isHidden = false - } else { - targetView.alpha = 1.0 - targetView.isHidden = false + if isLarge { + strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: true, completion: { + if let addStandaloneReactionAnimation = addStandaloneReactionAnimation { + let standaloneReactionAnimation = StandaloneReactionAnimation() + + 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 additionalAnimationCompleted = true intermediateCompletion() - 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) { self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y) transition.animateOffsetAdditive(node: self, offset: -offset.y) @@ -1001,7 +1096,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.isCancelled = true if let targetView = self.targetView { - if let targetView = targetView as? ReactionIconView { + if let targetView = targetView as? ReactionIconView, self.itemNodeIsEmbedded { targetView.imageView.isHidden = false } else { targetView.alpha = 1.0 diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index bce90e3253..d22f70ff13 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -130,8 +130,12 @@ final class ReactionNode: ASDisplayNode { self.animationNode = animationNode self.addSubnode(animationNode) + var didReportStarted = false animationNode.started = { [weak self] in - self?.expandedAnimationDidBegin?() + if !didReportStarted { + didReportStarted = true + self?.expandedAnimationDidBegin?() + } } if largeExpanded { diff --git a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift index 34bd0a559e..ae1243ddbc 100644 --- a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift @@ -170,7 +170,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode { applicationAnimation: aroundAnimation, largeApplicationAnimation: reaction.effectAnimation ), + isLarge: false, targetView: targetView, + addStandaloneReactionAnimation: nil, completion: { [weak standaloneReactionAnimation] in standaloneReactionAnimation?.removeFromSupernode() } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index c958e096b6..04ded46aa1 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -877,7 +877,13 @@ public final class AccountViewTracker { if !added { 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: break @@ -1167,6 +1173,23 @@ public final class AccountViewTracker { } let _ = (account.postbox.transaction { transaction -> Set in 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 { var maxId: Int32 = summary.range.maxId if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { @@ -1179,9 +1202,7 @@ public final class AccountViewTracker { return ids } - |> deliverOn(self.queue)).start(next: { _ in - //self?.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) - }) + |> deliverOn(self.queue)).start() } } @@ -1235,7 +1256,24 @@ public final class AccountViewTracker { } let _ = (account.postbox.transaction { transaction -> Set in let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id })) - if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud), summary.count > 0 { + + 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 if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { maxId = index.id.id diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift index c33ca8cd98..de44b60363 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -283,7 +283,33 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else { return .complete() } - let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) + + let signal = network.request(Api.functions.messages.readReactions(peer: inputPeer)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .fail(true) + } + |> mapToSignal { result -> Signal 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 in + return .complete() + } + + /*let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) let limit: Int32 = 100 let oneOperation: (Int32) -> Signal = { 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)) @@ -365,7 +391,7 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox return loopOperations |> `catch` { _ -> Signal in return .complete() - } + }*/ } func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) { @@ -386,7 +412,9 @@ func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynch 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 { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 9d0f2d2e2d..37d70d039d 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -274,6 +274,7 @@ public enum PresentationResourceParameterKey: Hashable { case chatListBadgeBackgroundActive(CGFloat) case chatListBadgeBackgroundInactive(CGFloat) case chatListBadgeBackgroundMention(CGFloat) + case badgeBackgroundReactions(CGFloat) case chatListBadgeBackgroundInactiveMention(CGFloat) case chatListBadgeBackgroundPinned(CGFloat) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 96e88b8502..64376ef2c1 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -683,7 +683,7 @@ public struct PresentationResourcesChat { 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))) - 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)) } }) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index 2732bdbffa..b57c31f916 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -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? { 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)) diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/Contents.json new file mode 100644 index 0000000000..a9b25a55ee --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "reactionbadge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/reactionbadge.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/reactionbadge.pdf new file mode 100644 index 0000000000..fccb268933 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/ReactionsBadgeIcon.imageset/reactionbadge.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/Contents.json new file mode 100644 index 0000000000..924e29571b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "reactionbutton.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/reactionbutton.pdf b/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/reactionbutton.pdf new file mode 100644 index 0000000000..19c264dd3e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/NavigateToReactions.imageset/reactionbutton.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b075d086b5..0e030589cf 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1294,7 +1294,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G applicationAnimation: aroundAnimation, largeApplicationAnimation: reaction.effectAnimation ), + isLarge: false, 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 standaloneReactionAnimation?.removeFromSupernode() } @@ -5944,7 +5953,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G applicationAnimation: aroundAnimation, largeApplicationAnimation: reaction.effectAnimation ), + isLarge: false, 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 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) { self.loadingMessage.set(.single(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) - 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 { let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode)) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 6dfa244e02..d998da2471 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1344,6 +1344,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty { let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen + + let _ = strongSelf.displayUnseenReactionAnimations(messageIds: Array(messageIds)) + strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll() 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 if let strongSelf = self { - var newIncomingReactions: [MessageId: String] = [:] + var newIncomingReactions: [MessageId: (value: String, isLarge: Bool)] = [:] 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 { switch entry { case let .MessageEntry(message, _, _, _, _, _): @@ -2393,7 +2396,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let reactions = message.reactionsAttribute { for recentPeer in reactions.recentPeers { 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 { for recentPeer in reactions.recentPeers { 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 } } @@ -2441,7 +2444,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } - if previousReaction != updatedReaction { + if previousReaction != updatedReaction.value { 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 { - var visibleNewIncomingReactionMessageIds: [MessageId] = [] - 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() - } - ) - } - } - } - } + if !newIncomingReactions.isEmpty { + let messageIds = Array(newIncomingReactions.keys) + let visibleNewIncomingReactionMessageIds = strongSelf.displayUnseenReactionAnimations(messageIds: messageIds) if !visibleNewIncomingReactionMessageIds.isEmpty { 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) { self.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {}) } @@ -2964,6 +2998,29 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { 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 selectionScrollActivationTimer: SwiftSignalKit.Timer? private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator?