diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 57b5f847e0..620dd7fdc8 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -67,6 +67,8 @@ private extension ComponentTransition.Animation.Curve { switch self { case .easeInOut: return CAMediaTimingFunction(name: .easeInEaseOut) + case .easeIn: + return CAMediaTimingFunction(name: .easeIn) case .linear: return CAMediaTimingFunction(name: .linear) case let .custom(a, b, c, d): @@ -82,6 +84,8 @@ private extension ComponentTransition.Animation.Curve { return [.curveLinear] case .easeInOut: return [.curveEaseInOut] + case .easeIn: + return [.curveEaseIn] case .spring: return UIView.AnimationOptions(rawValue: 7 << 16) case .custom: @@ -141,6 +145,7 @@ public struct ComponentTransition { public enum Animation { public enum Curve { case easeInOut + case easeIn case spring case linear case custom(Float, Float, Float, Float) @@ -150,6 +155,8 @@ public struct ComponentTransition { switch self { case .easeInOut: return listViewAnimationCurveEaseInOut(offset) + case .easeIn: + return listViewAnimationCurveEaseIn(offset) case .spring: return listViewAnimationCurveSystem(offset) case .linear: diff --git a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift index 7e2e790e57..29ae9b147f 100644 --- a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift +++ b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift @@ -10,6 +10,8 @@ public extension ComponentTransition.Animation.Curve { self = .linear case .easeInOut: self = .easeInOut + case .easeIn: + self = .easeIn case let .custom(a, b, c, d): self = .custom(a, b, c, d) case .customSpring: @@ -25,6 +27,8 @@ public extension ComponentTransition.Animation.Curve { return .linear case .easeInOut: return .easeInOut + case .easeIn: + return .easeIn case .spring: return .spring case let .custom(a, b, c, d): diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 30add28045..ceb65eb6fd 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -12,6 +12,7 @@ extension CGRect { public enum ContainedViewLayoutTransitionCurve: Equatable, Hashable { case linear case easeInOut + case easeIn case spring case customSpring(damping: CGFloat, initialVelocity: CGFloat) case custom(Float, Float, Float, Float) @@ -28,6 +29,8 @@ public extension ContainedViewLayoutTransitionCurve { return offset case .easeInOut: return listViewAnimationCurveEaseInOut(offset) + case .easeIn: + return listViewAnimationCurveEaseIn(offset) case .spring: return listViewAnimationCurveSystem(offset) case .customSpring: @@ -45,6 +48,8 @@ public extension ContainedViewLayoutTransitionCurve { return CAMediaTimingFunctionName.linear.rawValue case .easeInOut: return CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + return CAMediaTimingFunctionName.easeIn.rawValue case .spring: return kCAMediaTimingFunctionSpring case let .customSpring(damping, initialVelocity): @@ -60,6 +65,8 @@ public extension ContainedViewLayoutTransitionCurve { return nil case .easeInOut: return nil + case .easeIn: + return nil case .spring: return nil case .customSpring: @@ -75,6 +82,8 @@ public extension ContainedViewLayoutTransitionCurve { return [.curveLinear] case .easeInOut: return [.curveEaseInOut] + case .easeIn: + return [.curveEaseIn] case .spring: return UIView.AnimationOptions(rawValue: 7 << 16) case .customSpring: diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 76187edacc..9696585147 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -3981,6 +3981,12 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } else { headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) } + case .easeIn: + if transition.1 { + headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction) + } else { + headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)) + } } } diff --git a/submodules/Display/Source/ListViewAnimation.swift b/submodules/Display/Source/ListViewAnimation.swift index 5c4b10f394..52e3b4c9c8 100644 --- a/submodules/Display/Source/ListViewAnimation.swift +++ b/submodules/Display/Source/ListViewAnimation.swift @@ -219,6 +219,8 @@ public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTra return (animationDuration, .Default(duration: animationDuration)) case .easeInOut: return (animationDuration, .Default(duration: animationDuration)) + case .easeIn: + return (animationDuration, .Default(duration: animationDuration)) case .spring, .customSpring: return (animationDuration, .Spring(duration: animationDuration)) case let .custom(c1x, c1y, c2x, c2y): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift index b53c8606d3..5a936112a3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift @@ -100,11 +100,13 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag for (_, index) in readMessageIndexByNamespace { if let threadId { + var newCountIsZero = false if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if index.id.id >= data.maxIncomingReadId { if let count = transaction.getThreadMessageCount(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, fromIdExclusive: data.maxIncomingReadId, toIndex: index) { data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count)) data.maxIncomingReadId = index.id.id + newCountIsZero = data.incomingUnreadCount == 0 } if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { @@ -124,6 +126,23 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag } } } + + if newCountIsZero, let peer = transaction.getPeer(peerId), peer.isForumOrMonoForum { + var allTopicsAreRead = true + for item in transaction.getMessageHistoryThreadIndex(peerId: peer.id, limit: 100) { + guard let data = transaction.getMessageHistoryThreadInfo(peerId: index.id.peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { + continue + } + if data.incomingUnreadCount != 0 { + allTopicsAreRead = false + break + } + } + + if allTopicsAreRead { + _internal_applyMaxReadIndexInteractively(transaction: transaction, stateManager: stateManager, index: index) + } + } } else { _internal_applyMaxReadIndexInteractively(transaction: transaction, stateManager: stateManager, index: index) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 404693261f..1d6e1927c1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -339,7 +339,6 @@ private class ReplyThreadHistoryContextImpl { } var markMainAsRead = false - markMainAsRead = !"".isEmpty if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if messageIndex.id.id >= data.maxIncomingReadId { @@ -348,6 +347,7 @@ private class ReplyThreadHistoryContextImpl { data.maxIncomingReadId = messageIndex.id.id } + var newCountIsZero = false if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { if messageIndex.id.id >= topMessageIndex.id.id { let containingHole = transaction.getThreadIndexHole(peerId: peerId, threadId: threadId, namespace: topMessageIndex.id.namespace, containing: topMessageIndex.id.id) @@ -357,6 +357,24 @@ private class ReplyThreadHistoryContextImpl { } } } + newCountIsZero = data.incomingUnreadCount == 0 + + if newCountIsZero, let peer = transaction.getPeer(peerId), peer.isForumOrMonoForum { + var allTopicsAreRead = true + for item in transaction.getMessageHistoryThreadIndex(peerId: peer.id, limit: 100) { + guard let data = transaction.getMessageHistoryThreadInfo(peerId: messageIndex.id.peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { + continue + } + if data.incomingUnreadCount != 0 { + allTopicsAreRead = false + break + } + } + + if allTopicsAreRead { + markMainAsRead = true + } + } data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) diff --git a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift index cd85867620..cc9cf65b6a 100644 --- a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift +++ b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift @@ -576,7 +576,7 @@ public final class AsyncListComponent: Component { case let .curve(duration, curve): updateSizeAndInsets.duration = duration switch curve { - case .linear, .easeInOut: + case .linear, .easeInOut, .easeIn: updateSizeAndInsets.curve = .Default(duration: duration) case .spring: updateSizeAndInsets.curve = .Spring(duration: duration) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 000d153ca8..333178e1de 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -252,7 +252,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else { currentMaxGlyphCount = nil } - let previousGlyphCount = self.textNode.textNode.getCharacterToGlyphMapping().count + let previousGlyphCount = self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 return { item, layoutConstants, _, _, _, _ in let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -711,7 +711,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { lineColor: messageTheme.accentControlColor, displayContentsUnderSpoilers: displayContentsUnderSpoilers.value, customTruncationToken: customTruncationToken, - expandedBlocks: expandedBlockIds + expandedBlocks: expandedBlockIds, + computeCharacterRects: true )) var hasDraft = false @@ -729,7 +730,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } var clippedGlyphCountLayout: TextNodeLayout.LayoutInfo? if let maxGlyphCount { - clippedGlyphCountLayout = textLayout.layoutForGlyphCount(glyphCount: maxGlyphCount) + clippedGlyphCountLayout = textLayout.layoutForCharacterCount(characterCount: maxGlyphCount) } var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))? @@ -826,7 +827,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var previousAnimateGlyphCount: Int? if hasDraft || hadDraft { - previousAnimateGlyphCount = strongSelf.textNode.textNode.getCharacterToGlyphMapping().count + previousAnimateGlyphCount = strongSelf.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 } strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview @@ -1052,8 +1053,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } if previousAnimateGlyphCount != nil || strongSelf.textRevealAnimationState != nil || hasDraft || hadDraft { - if strongSelf.textNode.textNode.revealGlyphCount == nil { - strongSelf.textNode.textNode.updateRevealGlyphCount(count: previousAnimateGlyphCount ?? 0) + if strongSelf.textNode.textNode.revealCharacterCount == nil { + strongSelf.textNode.textNode.updateRevealCharacterCount(count: previousAnimateGlyphCount ?? 0, animated: false) } strongSelf.updateTextRevealAnimation(previousGlyphCount: previousAnimateGlyphCount ?? 0) } @@ -1066,7 +1067,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private func updateTextRevealAnimation(previousGlyphCount: Int) { var fromCount = previousGlyphCount - let toCount = self.textNode.textNode.getCharacterToGlyphMapping().count + let toCount = self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 let timestamp = CACurrentMediaTime() if let textRevealAnimationState = self.textRevealAnimationState { if textRevealAnimationState.toCount == toCount { @@ -1078,7 +1079,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if self.textRevealAnimationState != nil { self.textRevealAnimationState = nil self.textRevealLink = nil - self.textNode.textNode.updateRevealGlyphCount(count: nil) + self.textNode.textNode.updateRevealCharacterCount(count: nil, animated: false) } return } @@ -1106,14 +1107,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { self.textRevealAnimationState = nil self.textRevealLink = nil - self.textNode.textNode.updateRevealGlyphCount(count: nil) + self.textNode.textNode.updateRevealCharacterCount(count: nil, animated: false) self.requestFullUpdate?() } else { var requestUpdate = false let glyphCount = textRevealAnimationState.glyphCount(timestamp: timestamp) - if let previousRevealGlyphCount = self.textNode.textNode.revealGlyphCount, previousRevealGlyphCount != glyphCount { + if let previousRevealGlyphCount = self.textNode.textNode.revealCharacterCount, previousRevealGlyphCount != glyphCount { if let cachedLayout = self.textNode.textNode.cachedLayout { - if cachedLayout.sizeForGlyphCount(glyphCount: previousRevealGlyphCount) != cachedLayout.sizeForGlyphCount(glyphCount: glyphCount) { + if cachedLayout.sizeForCharacterCount(characterCount: previousRevealGlyphCount) != cachedLayout.sizeForCharacterCount(characterCount: glyphCount) { requestUpdate = true } } else { @@ -1123,7 +1124,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { //print("glyphCount: request update") } - self.textNode.textNode.updateRevealGlyphCount(count: glyphCount) + self.textNode.textNode.updateRevealCharacterCount(count: glyphCount, animated: true) } if requestUpdate { diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift index aab9c7a485..d7b8b56750 100644 --- a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift @@ -2985,15 +2985,40 @@ final class TextContentItemLayer: SimpleLayer { self.addSublayer(snippetLayer) self.animatingSnippetLayers.append(snippetLayer) - snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) - snippetLayer.animateSpring(from: 0.001, to: 1.0, keyPath: "transform.scale", duration: 0.5, completion: { [weak self, weak snippetLayer] _ in - guard let self, let snippetLayer else { - return - } - snippetLayer.removeFromSuperlayer() - self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) - self.updateRevealMask(animateNewSegments: false) - }) + if !"".isEmpty { + //ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)).animateBlur(layer: snippetLayer, fromRadius: 10.0, toRadius: 0.0) + + snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + snippetLayer.animateScale(from: 0.1, to: 1.0, duration: 0.2, completion: { [weak self, weak snippetLayer] _ in + guard let self, let snippetLayer else { + return + } + snippetLayer.removeFromSuperlayer() + self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) + self.updateRevealMask(animateNewSegments: false) + }) + } else if "".isEmpty { + ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut)).animateBlur(layer: snippetLayer, fromRadius: 8.0, toRadius: 0.0) + snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + snippetLayer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: CGPoint(), duration: 0.2, additive: true) + snippetLayer.animateScale(from: 0.5, to: 1.0, duration: 0.2, completion: { [weak self, weak snippetLayer] _ in + guard let self, let snippetLayer else { + return + } + snippetLayer.removeFromSuperlayer() + self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) + self.updateRevealMask(animateNewSegments: false) + }) + } else { + snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self, weak snippetLayer] _ in + guard let self, let snippetLayer else { + return + } + snippetLayer.removeFromSuperlayer() + self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) + self.updateRevealMask(animateNewSegments: false) + }) + } } globalCharIndex += lineCharCount } diff --git a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift index 7e7f049436..e4607c1e70 100644 --- a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift +++ b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift @@ -504,7 +504,9 @@ final class MiniAppListScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index f9a8a6cdf4..355728c104 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -1064,7 +1064,9 @@ final class QuickReplySetupScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift index 0cb04d686f..d0fa4a8c28 100644 --- a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift @@ -522,7 +522,9 @@ final class PeerSelectionScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift b/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift index 0b735bed62..e65b81b067 100644 --- a/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift +++ b/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift @@ -74,6 +74,8 @@ public final class AnimatableProperty { break case .easeInOut: t = listViewAnimationCurveEaseInOut(t) + case .easeIn: + t = listViewAnimationCurveEaseIn(t) case .spring: t = lookupSpringValue(t) case let .custom(x1, y1, x2, y2):