mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-25 17:43:18 +00:00
Reaction improvements
This commit is contained in:
parent
afcf6a89d0
commit
007ed84bc3
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -1423,13 +1423,16 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
|
||||
|
||||
let effectFrame: CGRect
|
||||
var effectFrame: CGRect
|
||||
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
||||
if self.didTriggerExpandedReaction {
|
||||
let expandFactor: CGFloat = 0.5
|
||||
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
||||
} else {
|
||||
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||
if itemNode.item.isCustom {
|
||||
effectFrame = effectFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||
@ -1442,11 +1445,28 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
|
||||
var genericAnimationView: AnimationView?
|
||||
|
||||
let additionalAnimation: TelegramMediaFile?
|
||||
var additionalAnimation: TelegramMediaFile?
|
||||
if self.didTriggerExpandedReaction {
|
||||
additionalAnimation = itemNode.item.largeApplicationAnimation
|
||||
} else {
|
||||
additionalAnimation = itemNode.item.applicationAnimation
|
||||
|
||||
if additionalAnimation == nil && itemNode.item.isCustom {
|
||||
outer: for attribute in itemNode.item.stillAnimation.attributes {
|
||||
if case let .CustomEmoji(_, alt, _) = attribute {
|
||||
if let availableReactions = self.availableReactions {
|
||||
for availableReaction in availableReactions.reactions {
|
||||
if availableReaction.value == .builtin(alt) {
|
||||
additionalAnimation = availableReaction.aroundAnimation
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let additionalAnimation = additionalAnimation {
|
||||
@ -1676,6 +1696,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
recognizer.state = .cancelled
|
||||
return
|
||||
}
|
||||
if !itemNode.isAnimationLoaded {
|
||||
recognizer.state = .cancelled
|
||||
return
|
||||
}
|
||||
|
||||
self.highlightedReaction = itemNode.item.reaction
|
||||
if #available(iOS 13.0, *) {
|
||||
@ -1871,6 +1895,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
||||
let itemNode = self.reactionItemNode(at: point)
|
||||
if let itemNode = itemNode as? ReactionNode {
|
||||
if !itemNode.isAnimationLoaded {
|
||||
return nil
|
||||
}
|
||||
return .reaction(itemNode.item)
|
||||
} else if let _ = itemNode as? PremiumReactionsNode {
|
||||
return .premium
|
||||
|
||||
@ -12,6 +12,7 @@ import StickerResources
|
||||
import AccountContext
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ShimmerEffect
|
||||
|
||||
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
|
||||
@ -48,6 +49,7 @@ protocol ReactionItemNode: ASDisplayNode {
|
||||
|
||||
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let item: ReactionItem
|
||||
private let loopIdle: Bool
|
||||
private let hasAppearAnimation: Bool
|
||||
@ -57,6 +59,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
let selectionView: UIView
|
||||
|
||||
private var animateInAnimationNode: AnimatedStickerNode?
|
||||
private var staticAnimationPlaceholderView: UIImageView?
|
||||
private let staticAnimationNode: AnimatedStickerNode
|
||||
private var stillAnimationNode: AnimatedStickerNode?
|
||||
private var customContentsNode: ASDisplayNode?
|
||||
@ -83,8 +86,13 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
return self.staticAnimationNode.currentFrameImage
|
||||
}
|
||||
|
||||
var isAnimationLoaded: Bool {
|
||||
return self.staticAnimationNode.currentFrameImage != nil
|
||||
}
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.item = item
|
||||
self.loopIdle = loopIdle
|
||||
self.hasAppearAnimation = hasAppearAnimation
|
||||
@ -361,6 +369,33 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
if self.animationNode == nil {
|
||||
self.didSetupStillAnimation = true
|
||||
|
||||
let staticFile: TelegramMediaFile
|
||||
if !self.hasAppearAnimation {
|
||||
staticFile = self.item.largeListAnimation
|
||||
} else {
|
||||
staticFile = self.item.stillAnimation
|
||||
}
|
||||
|
||||
if self.staticAnimationPlaceholderView == nil, let immediateThumbnailData = staticFile.immediateThumbnailData {
|
||||
let staticAnimationPlaceholderView = UIImageView()
|
||||
self.view.addSubview(staticAnimationPlaceholderView)
|
||||
self.staticAnimationPlaceholderView = staticAnimationPlaceholderView
|
||||
|
||||
if let image = generateStickerPlaceholderImage(data: immediateThumbnailData, size: animationDisplaySize, scale: min(2.0, UIScreenScale), imageSize: staticFile.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: self.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1)) {
|
||||
staticAnimationPlaceholderView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
self.staticAnimationNode.started = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let staticAnimationPlaceholderView = strongSelf.staticAnimationPlaceholderView {
|
||||
strongSelf.staticAnimationPlaceholderView = nil
|
||||
staticAnimationPlaceholderView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
self.staticAnimationNode.automaticallyLoadFirstFrame = true
|
||||
if !self.hasAppearAnimation {
|
||||
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
@ -372,6 +407,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.staticAnimationNode.updateLayout(size: animationFrame.size)
|
||||
self.staticAnimationNode.visibility = true
|
||||
|
||||
if let staticAnimationPlaceholderView = self.staticAnimationPlaceholderView {
|
||||
staticAnimationPlaceholderView.center = animationFrame.center
|
||||
staticAnimationPlaceholderView.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||
}
|
||||
|
||||
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker || self.item.appearAnimation.isStaticSticker || self.item.appearAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
|
||||
animateInAnimationNode.position = animationFrame.center
|
||||
@ -383,6 +423,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
transition.updatePosition(node: self.staticAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
||||
transition.updateTransformScale(node: self.staticAnimationNode, scale: animationFrame.size.width / self.staticAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||
|
||||
if let staticAnimationPlaceholderView = self.staticAnimationPlaceholderView {
|
||||
transition.updatePosition(layer: staticAnimationPlaceholderView.layer, position: animationFrame.center)
|
||||
transition.updateTransformScale(layer: staticAnimationPlaceholderView.layer, scale: animationFrame.size.width / self.staticAnimationNode.bounds.width)
|
||||
}
|
||||
|
||||
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||
transition.updatePosition(node: animateInAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
||||
transition.updateTransformScale(node: animateInAnimationNode, scale: animationFrame.size.width / animateInAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||
|
||||
@ -66,12 +66,17 @@ public func mergedMessageReactionsAndPeers(accountPeer: EnginePeer?, message: Me
|
||||
|
||||
if message.id.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
for reaction in attribute.reactions {
|
||||
var selfCount: Int32 = 0
|
||||
if reaction.isSelected {
|
||||
selfCount += 1
|
||||
if let accountPeer = accountPeer {
|
||||
recentPeers.append((reaction.value, accountPeer))
|
||||
}
|
||||
} else if let peer = message.peers[message.id.peerId] {
|
||||
recentPeers.append((reaction.value, EnginePeer(peer)))
|
||||
}
|
||||
if reaction.count > selfCount + 1 {
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
recentPeers.append((reaction.value, EnginePeer(peer)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1629,6 +1629,15 @@ func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set<Int64>) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
for reaction in attribute.reactions {
|
||||
switch reaction.value {
|
||||
case let .custom(fileId):
|
||||
fileIds.insert(fileId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1648,10 +1657,22 @@ private func messagesFromOperations(state: AccountMutableState) -> [StoreMessage
|
||||
return messages
|
||||
}
|
||||
|
||||
private func reactionsFromState(_ state: AccountMutableState) -> [MessageReaction.Reaction] {
|
||||
var result: [MessageReaction.Reaction] = []
|
||||
for operation in state.operations {
|
||||
if case let .UpdateMessageReactions(_, reactions, _) = operation {
|
||||
for reaction in ReactionsMessageAttribute(apiReactions: reactions).reactions {
|
||||
result.append(reaction.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func resolveAssociatedMessages(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
|
||||
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
|
||||
if missingMessageIds.isEmpty {
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), result: state)
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state)
|
||||
} else {
|
||||
var missingPeers = false
|
||||
let _ = missingPeers
|
||||
@ -1713,7 +1734,7 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state
|
||||
return updatedState
|
||||
}
|
||||
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), result: updatedState)
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), reactions: reactionsFromState(updatedState), result: updatedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +390,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
folderSummaries: folderSummaries,
|
||||
peerGroupIds: peerGroupIds
|
||||
)
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, result: result)
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, reactions: [], result: result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,13 +43,19 @@ enum FetchMessageHistoryHoleSource {
|
||||
}
|
||||
}
|
||||
|
||||
func resolveUnknownEmojiFiles<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], result: T) -> Signal<T, NoError> {
|
||||
func resolveUnknownEmojiFiles<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], reactions: [MessageReaction.Reaction], result: T) -> Signal<T, NoError> {
|
||||
var fileIds = Set<Int64>()
|
||||
|
||||
for message in messages {
|
||||
extractEmojiFileIds(message: message, fileIds: &fileIds)
|
||||
}
|
||||
|
||||
for reaction in reactions {
|
||||
if case let .custom(fileId) = reaction {
|
||||
fileIds.insert(fileId)
|
||||
}
|
||||
}
|
||||
|
||||
if fileIds.isEmpty {
|
||||
return .single(result)
|
||||
} else {
|
||||
@ -111,7 +117,7 @@ private func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMe
|
||||
referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds))
|
||||
|
||||
if referencedIds.isEmpty {
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, result: Void())
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void())
|
||||
|> mapToSignal { _ -> Signal<T, NoError> in
|
||||
return postbox.transaction { transaction -> T in
|
||||
return f(transaction, [], [])
|
||||
@ -174,7 +180,7 @@ private func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMe
|
||||
}
|
||||
}
|
||||
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, result: Void())
|
||||
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void())
|
||||
|> mapToSignal { _ -> Signal<T, NoError> in
|
||||
return postbox.transaction { transaction -> T in
|
||||
return f(transaction, additionalPeers, additionalMessages)
|
||||
|
||||
@ -1390,6 +1390,12 @@ private func findHigherResolutionFileForAdaptation(itemDirectoryPath: String, ba
|
||||
|
||||
public final class AnimationCacheImpl: AnimationCache {
|
||||
private final class Impl {
|
||||
private struct ItemKey: Hashable {
|
||||
var id: String
|
||||
var width: Int
|
||||
var height: Int
|
||||
}
|
||||
|
||||
private final class ItemContext {
|
||||
let subscribers = Bag<(AnimationCacheItemResult) -> Void>()
|
||||
let disposable = MetaDisposable()
|
||||
@ -1406,7 +1412,7 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
private let fetchQueues: [Queue]
|
||||
private var nextFetchQueueIndex: Int = 0
|
||||
|
||||
private var itemContexts: [String: ItemContext] = [:]
|
||||
private var itemContexts: [ItemKey: ItemContext] = [:]
|
||||
|
||||
init(queue: Queue, basePath: String, allocateTempFile: @escaping () -> String) {
|
||||
self.queue = queue
|
||||
@ -1437,14 +1443,15 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
let key = ItemKey(id: sourceId, width: Int(size.width), height: Int(size.height))
|
||||
|
||||
let itemContext: ItemContext
|
||||
var beginFetch = false
|
||||
if let current = self.itemContexts[sourceId] {
|
||||
if let current = self.itemContexts[key] {
|
||||
itemContext = current
|
||||
} else {
|
||||
itemContext = ItemContext()
|
||||
self.itemContexts[sourceId] = itemContext
|
||||
self.itemContexts[key] = itemContext
|
||||
beginFetch = true
|
||||
}
|
||||
|
||||
@ -1459,11 +1466,11 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
let allocateTempFile = self.allocateTempFile
|
||||
guard let writer = AnimationCacheItemWriterImpl(queue: self.fetchQueues[fetchQueueIndex % self.fetchQueues.count], allocateTempFile: self.allocateTempFile, completion: { [weak self, weak itemContext] result in
|
||||
queue.async {
|
||||
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[sourceId] else {
|
||||
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[key] else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.itemContexts.removeValue(forKey: sourceId)
|
||||
strongSelf.itemContexts.removeValue(forKey: key)
|
||||
|
||||
guard let result = result else {
|
||||
return
|
||||
@ -1503,13 +1510,13 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
|
||||
return ActionDisposable { [weak self, weak itemContext] in
|
||||
queue.async {
|
||||
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[sourceId] else {
|
||||
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[key] else {
|
||||
return
|
||||
}
|
||||
itemContext.subscribers.remove(index)
|
||||
if itemContext.subscribers.isEmpty {
|
||||
itemContext.disposable.dispose()
|
||||
strongSelf.itemContexts.removeValue(forKey: sourceId)
|
||||
strongSelf.itemContexts.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6718,6 +6718,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
switch result {
|
||||
case let .result(messageId):
|
||||
if let messageId = messageId {
|
||||
strongSelf.chatDisplayNode.historyNode.suspendReadingReactions = true
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), scrollPosition: .center(.top), completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -6743,34 +6744,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
guard let availableReactions = item.associatedData.availableReactions else {
|
||||
return
|
||||
}
|
||||
|
||||
var avatarPeers: [EnginePeer] = []
|
||||
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updatedReactionPeer = updatedReactionPeer {
|
||||
avatarPeers.append(updatedReactionPeer)
|
||||
}
|
||||
|
||||
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
|
||||
return
|
||||
}
|
||||
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(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: strongSelf.context,
|
||||
theme: strongSelf.presentationData.theme,
|
||||
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
|
||||
reaction: ReactionItem(
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
switch updatedReaction {
|
||||
case .builtin:
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == updatedReaction {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
@ -6779,28 +6774,60 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
),
|
||||
avatarPeers: avatarPeers,
|
||||
playHaptic: true,
|
||||
isLarge: updatedReactionIsLarge,
|
||||
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()
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: updatedReaction),
|
||||
appearAnimation: itemFile,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: itemFile,
|
||||
largeListAnimation: itemFile,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
guard let targetView = itemNode.targetReactionView(value: updatedReaction) else {
|
||||
return
|
||||
}
|
||||
if let reactionItem = reactionItem {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: strongSelf.context,
|
||||
theme: strongSelf.presentationData.theme,
|
||||
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
|
||||
reaction: reactionItem,
|
||||
avatarPeers: avatarPeers,
|
||||
playHaptic: true,
|
||||
isLarge: updatedReactionIsLarge,
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.historyNode.suspendReadingReactions = false
|
||||
})
|
||||
}
|
||||
case .loading:
|
||||
@ -17049,20 +17076,26 @@ func peerMessageAllowedReactions(context: AccountContext, message: Message) -> S
|
||||
return .set(Set(effectiveReactions.map(\.value)))
|
||||
}
|
||||
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
if let availableReactions = availableReactions {
|
||||
return .set(Set(availableReactions.reactions.map(\.value)))
|
||||
} else {
|
||||
return .set(Set())
|
||||
}
|
||||
}
|
||||
|
||||
switch allowedReactions {
|
||||
case .unknown:
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
if let availableReactions = availableReactions {
|
||||
return .set(Set(availableReactions.reactions.map(\.value)))
|
||||
} else {
|
||||
return .set(Set())
|
||||
}
|
||||
}
|
||||
return .all
|
||||
case let .known(value):
|
||||
switch value {
|
||||
case .all:
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
if let availableReactions = availableReactions {
|
||||
return .set(Set(availableReactions.reactions.map(\.value)))
|
||||
} else {
|
||||
return .set(Set())
|
||||
}
|
||||
}
|
||||
return .all
|
||||
case let .limited(reactions):
|
||||
return .set(Set(reactions))
|
||||
|
||||
@ -451,6 +451,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
let canReadHistory = Promise<Bool>()
|
||||
private var canReadHistoryValue: Bool = false
|
||||
private var canReadHistoryDisposable: Disposable?
|
||||
|
||||
var suspendReadingReactions: Bool = false {
|
||||
didSet {
|
||||
if self.suspendReadingReactions != oldValue {
|
||||
if !self.suspendReadingReactions {
|
||||
self.attemptReadingReactions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var messageIdsScheduledForMarkAsSeen = Set<MessageId>()
|
||||
private var messageIdsWithReactionsScheduledForMarkAsSeen = Set<MessageId>()
|
||||
@ -728,7 +738,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||
if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
|
||||
strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||
} else {
|
||||
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds)
|
||||
@ -1365,20 +1375,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
strongSelf.canReadHistoryValue = value
|
||||
strongSelf.updateReadHistoryActions()
|
||||
|
||||
if strongSelf.canReadHistoryValue && !strongSelf.messageIdsScheduledForMarkAsSeen.isEmpty {
|
||||
if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.messageIdsScheduledForMarkAsSeen.isEmpty {
|
||||
let messageIds = strongSelf.messageIdsScheduledForMarkAsSeen
|
||||
strongSelf.messageIdsScheduledForMarkAsSeen.removeAll()
|
||||
context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
strongSelf.attemptReadingReactions()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1598,6 +1601,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.genericReactionEffectDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func attemptReadingReactions() {
|
||||
if self.canReadHistoryValue && !self.suspendReadingReactions && !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !self.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
|
||||
let messageIds = self.messageIdsWithReactionsScheduledForMarkAsSeen
|
||||
|
||||
let _ = self.displayUnseenReactionAnimations(messageIds: Array(messageIds))
|
||||
|
||||
self.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
|
||||
self.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
|
||||
}
|
||||
}
|
||||
|
||||
func takeGenericReactionEffect() -> String? {
|
||||
let result = self.genericReactionEffect
|
||||
self.loadNextGenericReactionEffect(context: self.context)
|
||||
@ -2754,32 +2768,20 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
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(genericReactionEffect: self.genericReactionEffect)
|
||||
|
||||
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
var avatarPeers: [EnginePeer] = []
|
||||
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updateReactionPeer = updateReactionPeer {
|
||||
avatarPeers = [updateReactionPeer]
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
switch updatedReaction {
|
||||
case .builtin:
|
||||
if let availableReactions = item.associatedData.availableReactions {
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
|
||||
chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: self.context,
|
||||
theme: item.presentationData.theme.theme,
|
||||
animationCache: self.controllerInteraction.presentationContext.animationCache,
|
||||
reaction: ReactionItem(
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == updatedReaction {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
@ -2788,25 +2790,59 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
),
|
||||
avatarPeers: avatarPeers,
|
||||
playHaptic: true,
|
||||
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()
|
||||
}
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: updatedReaction),
|
||||
appearAnimation: itemFile,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: itemFile,
|
||||
largeListAnimation: itemFile,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionItem = reactionItem, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.genericReactionEffect)
|
||||
|
||||
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
var avatarPeers: [EnginePeer] = []
|
||||
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updateReactionPeer = updateReactionPeer {
|
||||
avatarPeers = [updateReactionPeer]
|
||||
}
|
||||
|
||||
chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: self.context,
|
||||
theme: item.presentationData.theme.theme,
|
||||
animationCache: self.controllerInteraction.presentationContext.animationCache,
|
||||
reaction: reactionItem,
|
||||
avatarPeers: avatarPeers,
|
||||
playHaptic: true,
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user