From 6b1a7d1aaeb525434305ade827ff39af2bc30fb0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 5 Jun 2022 19:24:41 +0400 Subject: [PATCH] Unread-based animation playback --- .../Sources/AnimatedStickerNode.swift | 5 + .../Postbox/Sources/MessageHistoryView.swift | 3 + .../Sources/ChatControllerInteraction.swift | 6 + .../Sources/ChatControllerNode.swift | 2 +- .../Sources/ChatHistoryListNode.swift | 94 +++++++++++---- .../ChatMessageAnimatedStickerItemNode.swift | 113 +++++++++++------- .../Sources/ChatMessageItemView.swift | 3 + .../ChatMessageTextBubbleContentNode.swift | 41 +------ .../Sources/GenerateTextEntities.swift | 5 +- 9 files changed, 167 insertions(+), 105 deletions(-) diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 1e4ba26a44..dbbf7b7cc9 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -375,6 +375,11 @@ public final class AnimatedStickerNode: ASDisplayNode { } private var isSetUpForPlayback = false + + public func playOnce() { + self.playbackMode = .once + self.play() + } public func play(firstFrame: Bool = false, fromIndex: Int? = nil) { if !firstFrame { diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index fb658e425a..04a3cf9518 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -936,6 +936,7 @@ public final class MessageHistoryView { public let entries: [MessageHistoryEntry] public let maxReadIndex: MessageIndex? public let fixedReadStates: MessageHistoryViewReadState? + public let transientReadStates: MessageHistoryViewReadState? public let topTaggedMessages: [Message] public let additionalData: [AdditionalMessageHistoryViewDataEntry] public let isLoading: Bool @@ -952,6 +953,7 @@ public final class MessageHistoryView { self.entries = entries self.maxReadIndex = nil self.fixedReadStates = nil + self.transientReadStates = nil self.topTaggedMessages = [] self.additionalData = [] self.isLoading = isLoading @@ -1053,6 +1055,7 @@ public final class MessageHistoryView { self.additionalData = mutableView.additionalDatas self.fixedReadStates = mutableView.combinedReadStates + self.transientReadStates = mutableView.transientReadStates switch mutableView.peerIds { case .single, .associated: diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index e2f8f16e7a..d950b8235e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -52,6 +52,11 @@ public enum ChatControllerInteractionReaction { case reaction(String) } +struct UnreadMessageRangeKey: Hashable { + var peerId: PeerId + var namespace: MessageId.Namespace +} + public final class ChatControllerInteraction { let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void @@ -149,6 +154,7 @@ public final class ChatControllerInteraction { var currentPsaMessageWithTooltip: MessageId? var stickerSettings: ChatInterfaceStickerSettings var searchTextHighightState: (String, [MessageIndex])? + var unreadMessageRange: [UnreadMessageRangeKey: Range] = [:] var seenOneTimeAnimatedMedia = Set() var currentMessageWithLoadingReplyThread: MessageId? var updatedPresentationData: (initial: PresentationData, signal: Signal)? diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index ff1e526019..4b963f0286 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2435,7 +2435,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { for text in breakChatInputText(trimChatInputText(inputText)) { if text.length != 0 { var attributes: [MessageAttribute] = [] - let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: Int(self.context.userLimits.maxAnimatedEmojisInText))) + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: 0/*Int(self.context.userLimits.maxAnimatedEmojisInText)*/)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 180406aec3..be7733f993 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1309,31 +1309,35 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let previousMaxIncomingMessageIndexByNamespace = Atomic<[MessageId.Namespace: MessageIndex]>(value: [:]) let readHistory = combineLatest(self.maxVisibleIncomingMessageIndex.get(), self.canReadHistory.get()) - |> map { messageIndex, canRead in - if canRead { - var apply = false - let _ = previousMaxIncomingMessageIndexByNamespace.modify { dict in - let previousIndex = dict[messageIndex.id.namespace] - if previousIndex == nil || previousIndex! < messageIndex { - apply = true - var dict = dict - dict[messageIndex.id.namespace] = messageIndex - return dict - } + + self.readHistoryDisposable.set((readHistory |> deliverOnMainQueue).start(next: { [weak self] messageIndex, canRead in + guard let strongSelf = self else { + return + } + if !canRead { + return + } + + var apply = false + let _ = previousMaxIncomingMessageIndexByNamespace.modify { dict in + let previousIndex = dict[messageIndex.id.namespace] + if previousIndex == nil || previousIndex! < messageIndex { + apply = true + var dict = dict + dict[messageIndex.id.namespace] = messageIndex return dict } - if apply { - switch chatLocation { - case .peer, .replyThread, .feed: - if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - context.applyMaxReadIndex(for: chatLocation, contextHolder: chatLocationContextHolder, messageIndex: messageIndex) - } + return dict + } + if apply { + switch chatLocation { + case .peer, .replyThread, .feed: + if !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory { + strongSelf.context.applyMaxReadIndex(for: chatLocation, contextHolder: chatLocationContextHolder, messageIndex: messageIndex) } } } - } - - self.readHistoryDisposable.set(readHistory.start()) + })) self.canReadHistoryDisposable = (self.canReadHistory.get() |> deliverOnMainQueue).start(next: { [weak self, weak context] value in if let strongSelf = self { @@ -2157,6 +2161,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return result } + public func forEachVisibleMessageItemNode(_ f: (ChatMessageItemView) -> Void) { + self.forEachVisibleItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + f(itemNode) + } + } + } + public func latestMessageInCurrentHistoryView() -> Message? { if let historyView = self.historyView { if historyView.originalView.laterId == nil, let firstEntry = historyView.filteredEntries.last { @@ -2369,6 +2381,42 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } + var unreadMessageRangeUpdated = false + + if case let .peer(peerId) = strongSelf.chatLocation, let previousReadStatesValue = strongSelf.historyView?.originalView.transientReadStates, case let .peer(previousReadStates) = previousReadStatesValue, case let .peer(updatedReadStates) = transition.historyView.originalView.transientReadStates { + if let previousPeerReadState = previousReadStates[peerId], let updatedPeerReadState = updatedReadStates[peerId] { + if previousPeerReadState != updatedPeerReadState { + for (namespace, state) in previousPeerReadState.states { + inner: for (updatedNamespace, updatedState) in updatedPeerReadState.states { + if namespace == updatedNamespace { + switch state { + case let .idBased(previousIncomingId, _, _, _, _): + if case let .idBased(updatedIncomingId, _, _, _, _) = updatedState, previousIncomingId <= updatedIncomingId { + let rangeKey = UnreadMessageRangeKey(peerId: peerId, namespace: namespace) + + if let currentRange = strongSelf.controllerInteraction.unreadMessageRange[rangeKey] { + if currentRange.upperBound < (updatedIncomingId + 1) { + strongSelf.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: peerId, namespace: namespace)] = currentRange.lowerBound ..< (updatedIncomingId + 1) + unreadMessageRangeUpdated = true + } + } else { + strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = (previousIncomingId + 1) ..< (updatedIncomingId + 1) + unreadMessageRangeUpdated = true + } + } + case .indexBased: + break + } + + break inner + } + } + } + //print("Read from \(previousPeerReadState) up to \(updatedPeerReadState)") + } + } + } + strongSelf.historyView = transition.historyView let loadState: ChatHistoryNodeLoadState @@ -2541,6 +2589,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } + if unreadMessageRangeUpdated { + strongSelf.forEachVisibleMessageItemNode { itemNode in + itemNode.unreadMessageRangeUpdated() + } + } + strongSelf.hasActiveTransition = false strongSelf.dequeueHistoryViewTransitions() } diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 66f66f1be0..65111fc0f7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -218,6 +218,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var currentSwipeAction: ChatControllerInteractionSwipeAction? + private var wasPending: Bool = false + private var didChangeFromPendingToSent: Bool = false + required init() { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() @@ -470,6 +473,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) + + if item.message.id.namespace == Namespaces.Message.Local { + self.wasPending = true + } + if self.wasPending && item.message.id.namespace != Namespaces.Message.Local { + self.didChangeFromPendingToSent = true + } for media in item.message.media { if let telegramFile = media as? TelegramMediaFile { @@ -526,7 +536,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false, synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad) self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start()) } - self.updateVisibility() let textEmoji = item.message.text.strippedEmoji var additionalTextEmoji = textEmoji @@ -551,6 +560,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } } + + self.updateVisibility() } private func updateVisibility() { @@ -558,6 +569,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return } + var file: TelegramMediaFile? + var playbackMode: AnimatedStickerPlaybackMode = .loop + var isEmoji = false + var fitzModifier: EmojiFitzModifier? + + if let telegramFile = self.telegramFile { + file = telegramFile + if !item.controllerInteraction.stickerSettings.loopAnimatedStickers { + playbackMode = .once + } + } else if let emojiFile = self.emojiFile { + isEmoji = true + file = emojiFile + //if alreadySeen && emojiFile.resource is LocalFileReferenceMediaResource { + playbackMode = .still(.end) + //} else { + // playbackMode = .once + //} + let (_, fitz) = item.message.text.basicEmoji + if let fitz = fitz { + fitzModifier = EmojiFitzModifier(emoji: fitz) + } + } + let isPlaying = self.visibilityStatus && !self.forceStopAnimations if let animationNode = self.animationNode as? AnimatedStickerNode { if !isPlaying { @@ -583,51 +618,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if self.isPlaying != isPlaying { self.isPlaying = isPlaying - var alreadySeen = false - if isPlaying, let _ = self.emojiFile { - if item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { - alreadySeen = true - } - } - if isPlaying && self.setupTimestamp == nil { self.setupTimestamp = CACurrentMediaTime() } animationNode.visibility = isPlaying - if self.didSetUpAnimationNode && alreadySeen { + /*if self.didSetUpAnimationNode && alreadySeen { if let emojiFile = self.emojiFile, emojiFile.resource is LocalFileReferenceMediaResource { } else { animationNode.seekTo(.start) } - } + }*/ if self.isPlaying && !self.didSetUpAnimationNode { self.didSetUpAnimationNode = true - - var file: TelegramMediaFile? - var playbackMode: AnimatedStickerPlaybackMode = .loop - var isEmoji = false - var fitzModifier: EmojiFitzModifier? - - if let telegramFile = self.telegramFile { - file = telegramFile - if !item.controllerInteraction.stickerSettings.loopAnimatedStickers { - playbackMode = .once - } - } else if let emojiFile = self.emojiFile { - isEmoji = true - file = emojiFile - if alreadySeen && emojiFile.resource is LocalFileReferenceMediaResource { - playbackMode = .still(.end) - } else { - playbackMode = .once - } - let (_, fitz) = item.message.text.basicEmoji - if let fitz = fitz { - fitzModifier = EmojiFitzModifier(emoji: fitz) - } - } if let file = file { let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) @@ -637,15 +641,39 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let mode: AnimatedStickerMode = .direct(cachePathPrefix: pathPrefix) self.animationSize = fittedSize animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, isVideo: file.isVideoSticker), width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: mode) - - if file.isPremiumSticker && !alreadySeen { - item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id) - Queue.mainQueue().after(0.1) { - self.playPremiumStickerAnimation() - } + } + } + } + } + + if isPlaying, let animationNode = self.animationNode as? AnimatedStickerNode { + var alreadySeen = true + if item.message.flags.contains(.Incoming) { + if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] { + if unreadRange.contains(item.message.id.id) { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false } } } + } else { + if self.didChangeFromPendingToSent { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false + } + } + } + + if !alreadySeen { + item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id) + if let file = file, file.isPremiumSticker { + Queue.mainQueue().after(0.1) { + self.playPremiumStickerAnimation() + } + } else if isEmoji { + animationNode.seekTo(.start) + animationNode.playOnce() + } } } } @@ -655,7 +683,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.updateVisibility() } - private var absoluteRect: (CGRect, CGSize)? override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) @@ -2378,6 +2405,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } return nil } + + override func unreadMessageRangeUpdated() { + self.updateVisibility() + } } struct AnimatedEmojiSoundsConfiguration { diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index ec9b4cea42..f34d924608 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -898,4 +898,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } } + + func unreadMessageRangeUpdated() { + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index 5c27588c37..4bd4d9df65 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -341,7 +341,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor) } - if let entities = entities { + /*if let entities = entities { let updatedString = NSMutableAttributedString(attributedString: attributedText) for entity in entities.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { @@ -368,48 +368,11 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { updatedAttributes[NSAttributedString.Key("Attribute__EmbeddedItem")] = InlineStickerItem(file: emojiFile) let insertString = NSAttributedString(string: "[\u{00a0}\u{00a0}]", attributes: updatedAttributes) - //updatedString.insert(insertString, at: NSRange(substringRange, in: updatedString.string).upperBound) updatedString.replaceCharacters(in: range, with: insertString) } - - /*var currentCount = 0 - let updatedString = NSMutableAttributedString(attributedString: attributedText) - var startIndex = updatedString.string.startIndex - while true { - var hadUpdates = false - updatedString.string.enumerateSubstrings(in: startIndex ..< updatedString.string.endIndex, options: [.byComposedCharacterSequences]) { substring, substringRange, _, stop in - if let substring = substring { - let emoji = substring.basicEmoji.0 - - var emojiFile: TelegramMediaFile? - emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file - if emojiFile == nil { - emojiFile = item.associatedData.animatedEmojiStickers[emoji.strippedEmoji]?.first?.file - } - - if let emojiFile = emojiFile { - let currentDict = updatedString.attributes(at: NSRange(substringRange, in: updatedString.string).lowerBound, effectiveRange: nil) - var updatedAttributes: [NSAttributedString.Key: Any] = currentDict - updatedAttributes[NSAttributedString.Key.foregroundColor] = UIColor.clear.cgColor - updatedAttributes[NSAttributedString.Key("Attribute__EmbeddedItem")] = InlineStickerItem(file: emojiFile) - - let insertString = NSAttributedString(string: "[\u{00a0}\u{00a0}\u{00a0}]", attributes: updatedAttributes) - //updatedString.insert(insertString, at: NSRange(substringRange, in: updatedString.string).upperBound) - updatedString.replaceCharacters(in: NSRange(substringRange, in: updatedString.string), with: insertString) - startIndex = substringRange.lowerBound - currentCount += 1 - hadUpdates = true - stop = true - } - } - } - if !hadUpdates || currentCount >= 10 { - break - } - }*/ } attributedText = updatedString - } + }*/ let cutout: TextNodeCutout? = nil diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index 7074cb85f5..dbaa90b7a6 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -146,7 +146,7 @@ private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimatedEmojisInText: Int? = nil) -> [MessageTextEntity] { var entities: [MessageTextEntity] = [] - if let maxAnimatedEmojisInText = maxAnimatedEmojisInText { + if let maxAnimatedEmojisInText = maxAnimatedEmojisInText, maxAnimatedEmojisInText != 0 { var count = 0 text.string.enumerateSubstrings(in: text.string.startIndex ..< text.string.endIndex, options: [.byComposedCharacterSequences], { substring, substringRange, _, stop in if let substring = substring { @@ -159,10 +159,7 @@ public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimate count += 1 if count >= maxAnimatedEmojisInText { - #if DEBUG - #else stop = true - #endif } } }