Dust effect improvements

This commit is contained in:
Ali 2023-11-18 01:48:34 +04:00
parent e25f926342
commit c89c1363ca
14 changed files with 291 additions and 238 deletions

View File

@ -71,6 +71,8 @@ public final class SharedDisplayLinkDriver {
private var requests: [RequestContext] = []
private var isInForeground: Bool = false
private var isProcessingEvent: Bool = false
private var isUpdateRequested: Bool = false
private init() {
let _ = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in
@ -110,7 +112,11 @@ public final class SharedDisplayLinkDriver {
}
private func requestUpdate() {
self.update()
if self.isProcessingEvent {
self.isUpdateRequested = true
} else {
self.update()
}
}
private func update() {
@ -153,6 +159,7 @@ public final class SharedDisplayLinkDriver {
}
if displayLink.preferredFrameRateRange != frameRateRange {
displayLink.preferredFrameRateRange = frameRateRange
print("SharedDisplayLinkDriver: switch to \(frameRateRange)")
}
}
}
@ -166,19 +173,26 @@ public final class SharedDisplayLinkDriver {
}
@objc private func displayLinkEvent(displayLink: CADisplayLink) {
let duration = displayLink.duration
self.isProcessingEvent = true
let duration = displayLink.targetTimestamp - displayLink.timestamp
var removeIndices: [Int]?
loop: for i in 0 ..< self.requests.count {
let request = self.requests[i]
if let link = request.link, link.isValid {
if !link.isPaused {
var itemDuration = duration
switch request.framesPerSecond {
case let .fps(value):
let secondsPerFrame = 1.0 / CGFloat(value)
itemDuration = secondsPerFrame
request.lastDuration += duration
if request.lastDuration >= secondsPerFrame * 0.99 {
if request.lastDuration >= secondsPerFrame * 0.95 {
//print("item \(link) accepting cycle: \(request.lastDuration - duration) + \(duration) = \(request.lastDuration) >= \(secondsPerFrame)")
} else {
//print("item \(link) skipping cycle: \(request.lastDuration - duration) + \(duration) < \(secondsPerFrame)")
continue loop
}
case .max:
@ -186,7 +200,7 @@ public final class SharedDisplayLinkDriver {
}
request.lastDuration = 0.0
link.update(duration)
link.update(itemDuration)
}
} else {
if removeIndices == nil {
@ -202,9 +216,15 @@ public final class SharedDisplayLinkDriver {
}
if self.requests.isEmpty {
self.update()
self.isUpdateRequested = true
}
}
self.isProcessingEvent = false
if self.isUpdateRequested {
self.isUpdateRequested = false
self.update()
}
}
public func add(framesPerSecond: FramesPerSecond = .fps(60), _ update: @escaping (CGFloat) -> Void) -> Link {

View File

@ -2436,8 +2436,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if node.index == nil {
var duration = insertionAnimationDuration
var hasCustomRemoveAnimation = false
if let value = self.customItemDeleteAnimationDuration(itemNode: node) {
duration = value
hasCustomRemoveAnimation = true
}
if node.animationForKey("height") == nil || !(node is ListViewTempItemNode) {
@ -2450,7 +2452,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
})
}
node.animateRemoved(timestamp, duration: duration * UIView.animationDurationFactor())
if !hasCustomRemoveAnimation {
node.animateRemoved(timestamp, duration: duration * UIView.animationDurationFactor())
}
} else if animated {
if takenAnimation {
if let previousFrame = previousFrame {

View File

@ -3,6 +3,7 @@ import UIKitRuntimeUtils
public class PortalView {
public let view: UIView & UIKitPortalViewProtocol
public weak var sourceView: UIView?
public init?(matchPosition: Bool = true) {
guard let view = makePortalView(matchPosition) else {
@ -13,6 +14,7 @@ public class PortalView {
func reloadPortal(sourceView: PortalSourceView) {
self.view.sourceView = sourceView
self.sourceView = sourceView
if let portalSuperview = self.view.superview, let index = portalSuperview.subviews.firstIndex(of: self.view) {
portalSuperview.insertSubview(self.view, at: index)

View File

@ -1,8 +1,9 @@
import Foundation
import UIKit
import Display
public final class ManagedAnimations {
private var displayLinkSubscription: SharedDisplayLink.Subscription?
private var displayLinkSubscription: SharedDisplayLinkDriver.Link?
private var properties: [AnyAnimatedProperty] = []
@ -23,7 +24,7 @@ public final class ManagedAnimations {
private func updateNeedAnimations() {
if self.displayLinkSubscription == nil {
self.displayLinkSubscription = SharedDisplayLink.shared.add { [weak self] in
self.displayLinkSubscription = SharedDisplayLinkDriver.shared.add { [weak self] _ in
guard let self else {
return
}

View File

@ -3,6 +3,7 @@ import MetalKit
import UIKit
import MetalEngine
import ComponentFlow
import Display
private func shiftArray(array: [SIMD2<Float>], offset: Int) -> [SIMD2<Float>] {
var newArray = array
@ -82,7 +83,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
private var phase: Float = 0.0
private var displayLinkSubscription: SharedDisplayLink.Subscription?
private var displayLinkSubscription: SharedDisplayLinkDriver.Link?
var renderSpec: RenderLayerSpec? {
didSet {
@ -128,7 +129,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
guard let self else {
return
}
self.displayLinkSubscription = SharedDisplayLink.shared.add(framesPerSecond: .fps(30.0), { [weak self] in
self.displayLinkSubscription = SharedDisplayLinkDriver.shared.add(framesPerSecond: .fps(30), { [weak self] timeDelta in
guard let self else {
return
}
@ -136,7 +137,7 @@ final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
self.phaseAcceleration.update()
let stepCount = 8
var phaseStep: CGFloat = 0.5 / 30.0
var phaseStep: CGFloat = 0.5 * timeDelta
phaseStep += phaseStep * self.phaseAcceleration.value * 0.5
self.phase = (self.phase + Float(phaseStep)).truncatingRemainder(dividingBy: Float(stepCount))

View File

@ -1,6 +1,7 @@
import Foundation
import MetalKit
import MetalEngine
import Display
final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject {
var internalData: MetalEngineSubjectInternalData?
@ -80,7 +81,7 @@ final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject {
private var blobs: [Blob] = []
private var displayLinkSubscription: SharedDisplayLink.Subscription?
private var displayLinkSubscription: SharedDisplayLinkDriver.Link?
override init() {
super.init()
@ -89,11 +90,11 @@ final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject {
guard let self else {
return
}
self.displayLinkSubscription = SharedDisplayLink.shared.add(framesPerSecond: .fps(30.0), { [weak self] in
self.displayLinkSubscription = SharedDisplayLinkDriver.shared.add(framesPerSecond: .fps(30), { [weak self] deltaTime in
guard let self else {
return
}
self.phase += 3.0 / 60.0
self.phase += 3.0 * Float(deltaTime)
if self.phase >= 1.0 {
for i in 0 ..< self.blobs.count {
self.blobs[i].advance()

View File

@ -1,6 +1,7 @@
import AVFoundation
import Metal
import CoreVideo
import Display
public final class VideoSourceOutput {
public let y: MTLTexture
@ -36,7 +37,7 @@ public final class FileVideoSource: VideoSource {
public private(set) var currentOutput: Output?
public var updated: (() -> Void)?
private var displayLink: SharedDisplayLink.Subscription?
private var displayLink: SharedDisplayLinkDriver.Link?
public var sourceId: Int = 0
@ -56,7 +57,7 @@ public final class FileVideoSource: VideoSource {
self.queuePlayer.play()
self.displayLink = SharedDisplayLink.shared.add(framesPerSecond: .fps(60.0), { [weak self] in
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .fps(60), { [weak self] _ in
guard let self else {
return
}

View File

@ -1,102 +0,0 @@
import Foundation
import UIKit
public final class SharedDisplayLink {
private final class DisplayLinkTarget: NSObject {
let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
}
@objc func event() {
self.f()
}
}
public enum FramesPerSecond {
case fps(Double)
case max
}
public final class Subscription {
fileprivate final class Target {
let event: () -> Void
let framesPerSecond: FramesPerSecond
var lastDuration: Double = 0.0
var totalTicks: Int = 0
var acceptedTicks: Int = 0
init(event: @escaping () -> Void, framesPerSecond: FramesPerSecond) {
self.event = event
self.framesPerSecond = framesPerSecond
}
}
fileprivate let target: Target
fileprivate init(event: @escaping () -> Void, framesPerSecond: FramesPerSecond) {
self.target = Target(event: event, framesPerSecond: framesPerSecond)
}
deinit {
SharedDisplayLink.shared.remove(target: self.target)
}
}
public static let shared: SharedDisplayLink = {
return SharedDisplayLink()
}()
private var displayLink: CADisplayLink?
private var subscriptions: [Subscription.Target] = []
private init() {
self.displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in
guard let self, let displayLink = self.displayLink else {
return
}
self.displayLinkEvent(timestamp: displayLink.timestamp, duration: displayLink.duration)
}, selector: #selector(DisplayLinkTarget.event))
if #available(iOS 15.0, *) {
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
}
self.displayLink?.add(to: .main, forMode: .common)
}
private func displayLinkEvent(timestamp: Double, duration: Double) {
loop: for subscription in self.subscriptions {
subscription.totalTicks += 1
switch subscription.framesPerSecond {
case let .fps(value):
let secondsPerFrame = 1.0 / value
subscription.lastDuration += duration
if subscription.lastDuration >= secondsPerFrame * 0.99 {
} else {
continue loop
}
case .max:
break
}
subscription.lastDuration = 0.0
subscription.acceptedTicks += 1
subscription.event()
}
}
public func add(framesPerSecond: FramesPerSecond = .max, _ event: @escaping () -> Void) -> Subscription {
let subscription = Subscription(event: event, framesPerSecond: framesPerSecond)
self.subscriptions.append(subscription.target)
return subscription
}
private func remove(target: Subscription.Target) {
if let index = self.subscriptions.firstIndex(where: { $0 === target }) {
self.subscriptions.remove(at: index)
}
}
}

View File

@ -5389,7 +5389,61 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let context = UIGraphicsGetCurrentContext()!
context.translateBy(x: -self.backgroundNode.frame.minX, y: -self.backgroundNode.frame.minY)
self.view.layer.render(in: context)
context.translateBy(x: -self.mainContextSourceNode.contentNode.view.frame.minX, y: -self.mainContextSourceNode.contentNode.view.frame.minY)
for subview in self.mainContextSourceNode.contentNode.view.subviews {
if subview.isHidden || subview.alpha == 0.0 {
continue
}
if subview === self.backgroundWallpaperNode.view {
var targetPortalView: UIView?
for backgroundSubview0 in subview.subviews {
for backgroundSubview1 in backgroundSubview0.subviews {
if isViewPortalView(backgroundSubview1) {
targetPortalView = backgroundSubview1
break
}
}
}
if let targetPortalView, let sourceView = getPortalViewSourceView(targetPortalView) {
context.saveGState()
context.translateBy(x: subview.frame.minX, y: subview.frame.minY)
if let mask = subview.mask {
let maskImage = generateImage(subview.bounds.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
mask.drawHierarchy(in: mask.frame, afterScreenUpdates: false)
UIGraphicsPopContext()
})
if let cgImage = maskImage?.cgImage {
context.translateBy(x: subview.frame.midX, y: subview.frame.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -subview.frame.midX, y: -subview.frame.midY)
context.clip(to: subview.bounds, mask: cgImage)
context.translateBy(x: subview.frame.midX, y: subview.frame.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -subview.frame.midX, y: -subview.frame.midY)
}
}
let sourceLocalFrame = sourceView.convert(sourceView.bounds, to: subview)
for sourceSubview in sourceView.subviews {
sourceSubview.drawHierarchy(in: CGRect(origin: sourceLocalFrame.origin, size: sourceSubview.bounds.size), afterScreenUpdates: false)
}
context.resetClip()
context.restoreGState()
} else {
subview.drawHierarchy(in: subview.frame, afterScreenUpdates: false)
}
} else {
subview.drawHierarchy(in: subview.frame, afterScreenUpdates: false)
}
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

View File

@ -108,6 +108,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
private var updateLink: SharedDisplayLinkDriver.Link?
private var items: [Item] = []
private var lastTimeStep: Double = 0.0
public var becameEmpty: (() -> Void)?
@ -139,10 +140,31 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
fatalError("init(coder:) has not been implemented")
}
private var lastUpdateTimestamp: Double?
private func updateItems(deltaTime: Double) {
let timestamp = CACurrentMediaTime()
let localDeltaTime: Double
if let lastUpdateTimestamp = self.lastUpdateTimestamp {
localDeltaTime = timestamp - lastUpdateTimestamp
} else {
localDeltaTime = 0.0
}
self.lastUpdateTimestamp = timestamp
let deltaTimeValue: Double
if localDeltaTime <= 0.001 || localDeltaTime >= 0.2 {
deltaTimeValue = deltaTime
} else {
deltaTimeValue = localDeltaTime
}
self.lastTimeStep = deltaTimeValue
//print("updateItems: \(deltaTime), localDeltaTime: \(localDeltaTime)")
var didRemoveItems = false
for i in (0 ..< self.items.count).reversed() {
self.items[i].phase += (1.0 / 60.0) / Float(UIView.animationDurationFactor())
self.items[i].phase += Float(deltaTimeValue) / Float(UIView.animationDurationFactor())
if self.items[i].phase >= 4.0 {
self.items.remove(at: i)
@ -217,6 +239,9 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
}
}
let lastTimeStep = self.lastTimeStep
self.lastTimeStep = 0.0
let _ = context.compute(state: DustComputeState.self, commands: { [weak self] commandBuffer, state in
guard let self else {
return
@ -245,14 +270,16 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}
computeEncoder.setComputePipelineState(state.computePipelineStateUpdateParticle)
var particleCount = SIMD2<UInt32>(UInt32(particleColumnCount), UInt32(particleRowCount))
computeEncoder.setBytes(&particleCount, length: 4 * 2, index: 1)
var phase = item.phase
computeEncoder.setBytes(&phase, length: 4, index: 2)
var timeStep: Float = (1.0 / 60.0) / Float(UIView.animationDurationFactor())
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
if lastTimeStep != 0.0 {
computeEncoder.setComputePipelineState(state.computePipelineStateUpdateParticle)
var particleCount = SIMD2<UInt32>(UInt32(particleColumnCount), UInt32(particleRowCount))
computeEncoder.setBytes(&particleCount, length: 4 * 2, index: 1)
var phase = item.phase
computeEncoder.setBytes(&phase, length: 4, index: 2)
var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor())
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}
}
computeEncoder.endEncoding()

View File

@ -17144,11 +17144,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
c.dismiss(completion: { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
guard let strongSelf else {
return
}
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
})
})
} else {
f(.dismissWithoutContent)

View File

@ -2861,24 +2861,114 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.hasActiveTransition = true
let transition = self.enqueuedHistoryViewTransitions.removeFirst()
var expiredMessageIds = Set<MessageId>()
if let previousHistoryView = self.historyView {
var existingIds = Set<MessageId>()
for entry in transition.historyView.filteredEntries {
switch entry {
case let .MessageEntry(message, _, _, _, _, _):
if message.autoremoveAttribute != nil {
existingIds.insert(message.id)
}
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if message.0.autoremoveAttribute != nil {
existingIds.insert(message.0.id)
}
}
default:
break
}
}
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent())
for entry in previousHistoryView.filteredEntries {
switch entry {
case let .MessageEntry(message, _, _, _, _, _):
if !existingIds.contains(message.id) {
if let autoremoveAttribute = message.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.id)
}
}
}
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if !existingIds.contains(message.0.id) {
if let autoremoveAttribute = message.0.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.0.id)
}
}
}
}
default:
break
}
}
}
self.currentDeleteAnimationCorrelationIds.formUnion(expiredMessageIds)
var appliedDeleteAnimationCorrelationIds = Set<MessageId>()
if !self.currentDeleteAnimationCorrelationIds.isEmpty {
var foundItemNodes: [ChatMessageItemView] = []
self.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if self.currentDeleteAnimationCorrelationIds.contains(message.id) {
appliedDeleteAnimationCorrelationIds.insert(message.id)
self.currentDeleteAnimationCorrelationIds.remove(message.id)
foundItemNodes.append(itemNode)
}
}
}
}
if !foundItemNodes.isEmpty {
if self.dustEffectLayer == nil {
let dustEffectLayer = DustEffectLayer()
dustEffectLayer.position = self.bounds.center
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
self.dustEffectLayer = dustEffectLayer
dustEffectLayer.zPosition = 10.0
dustEffectLayer.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
self.layer.addSublayer(dustEffectLayer)
dustEffectLayer.becameEmpty = { [weak self] in
guard let self else {
return
}
self.dustEffectLayer?.removeFromSuperlayer()
self.dustEffectLayer = nil
}
}
if let dustEffectLayer = self.dustEffectLayer {
for itemNode in foundItemNodes {
guard let (image, subFrame) = itemNode.makeContentSnapshot() else {
continue
}
let itemFrame = itemNode.layer.convert(subFrame, to: dustEffectLayer)
dustEffectLayer.addItem(frame: itemFrame, image: image)
itemNode.isHidden = true
}
}
}
}
self.currentAppliedDeleteAnimationCorrelationIds = appliedDeleteAnimationCorrelationIds
let animated = transition.options.contains(.AnimateInsertion)
let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in
if let strongSelf = self {
strongSelf.currentAppliedDeleteAnimationCorrelationIds.removeAll()
var newIncomingReactions: [MessageId: (value: MessageReaction.Reaction, isLarge: Bool)] = [:]
var expiredMessageIds = Set<MessageId>()
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent())
if case .peer = strongSelf.chatLocation, let previousHistoryView = strongSelf.historyView {
var updatedIncomingReactions: [MessageId: (value: MessageReaction.Reaction, isLarge: Bool)] = [:]
var existingIds = Set<MessageId>()
for entry in transition.historyView.filteredEntries {
switch entry {
case let .MessageEntry(message, _, _, _, _, _):
if message.autoremoveAttribute != nil {
existingIds.insert(message.id)
}
if message.flags.contains(.Incoming) {
continue
}
@ -2891,10 +2981,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if message.0.autoremoveAttribute != nil {
existingIds.insert(message.0.id)
}
if message.0.flags.contains(.Incoming) {
continue
}
@ -2926,14 +3012,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
newIncomingReactions[message.id] = updatedReaction
}
}
if !existingIds.contains(message.id) {
if let autoremoveAttribute = message.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.id)
}
}
}
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if let updatedReaction = updatedIncomingReactions[message.0.id] {
@ -2949,14 +3027,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
newIncomingReactions[message.0.id] = updatedReaction
}
}
if !existingIds.contains(message.0.id) {
if let autoremoveAttribute = message.0.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.0.id)
}
}
}
}
default:
break
@ -3200,54 +3270,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
var dustMessageIds = Set<MessageId>()
dustMessageIds.formUnion(expiredMessageIds)
if let currentDeleteAnimationCorrelationIds = strongSelf.currentDeleteAnimationCorrelationIds {
dustMessageIds.formUnion(currentDeleteAnimationCorrelationIds)
}
if !dustMessageIds.isEmpty {
var foundItemNodes: [ChatMessageItemView] = []
strongSelf.forEachRemovedItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if dustMessageIds.contains(message.id) {
foundItemNodes.append(itemNode)
}
}
}
}
if !foundItemNodes.isEmpty {
strongSelf.currentDeleteAnimationCorrelationIds = nil
if strongSelf.dustEffectLayer == nil {
let dustEffectLayer = DustEffectLayer()
dustEffectLayer.position = strongSelf.bounds.center
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: strongSelf.bounds.size)
strongSelf.dustEffectLayer = dustEffectLayer
dustEffectLayer.zPosition = 10.0
dustEffectLayer.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
strongSelf.layer.addSublayer(dustEffectLayer)
dustEffectLayer.becameEmpty = { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.dustEffectLayer?.removeFromSuperlayer()
strongSelf.dustEffectLayer = nil
}
}
if let dustEffectLayer = strongSelf.dustEffectLayer {
for itemNode in foundItemNodes {
guard let (image, subFrame) = itemNode.makeContentSnapshot() else {
continue
}
let itemFrame = itemNode.layer.convert(subFrame, to: dustEffectLayer)
dustEffectLayer.addItem(frame: itemFrame, image: image)
itemNode.isHidden = true
}
}
}
}
if !newIncomingReactions.isEmpty {
let messageIds = Array(newIncomingReactions.keys)
@ -3926,10 +3948,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.currentSendAnimationCorrelationIds = value
}
private var currentDeleteAnimationCorrelationIds: Set<MessageId>?
func setCurrentDeleteAnimationCorrelationIds(_ value: Set<MessageId>?) {
private var currentDeleteAnimationCorrelationIds = Set<MessageId>()
func setCurrentDeleteAnimationCorrelationIds(_ value: Set<MessageId>) {
self.currentDeleteAnimationCorrelationIds = value
}
private var currentAppliedDeleteAnimationCorrelationIds = Set<MessageId>()
var animationCorrelationMessagesFound: (([Int64: ChatMessageItemView]) -> Void)?
@ -4026,10 +4049,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
override public func customItemDeleteAnimationDuration(itemNode: ListViewItemNode) -> Double? {
if let currentDeleteAnimationCorrelationIds = self.currentDeleteAnimationCorrelationIds {
if !self.currentAppliedDeleteAnimationCorrelationIds.isEmpty {
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if currentDeleteAnimationCorrelationIds.contains(message.id) {
if self.currentAppliedDeleteAnimationCorrelationIds.contains(message.id) {
return 1.5
}
}

View File

@ -24,6 +24,8 @@ void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer);
@end
UIView<UIKitPortalViewProtocol> * _Nullable makePortalView(bool matchPosition);
bool isViewPortalView(UIView * _Nonnull view);
UIView * _Nullable getPortalViewSourceView(UIView * _Nonnull portalView);
NSObject * _Nullable makeBlurFilter();
NSObject * _Nullable makeLuminanceToAlphaFilter();

View File

@ -171,34 +171,51 @@ void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer) {
}
UIView<UIKitPortalViewProtocol> * _Nullable makePortalView(bool matchPosition) {
if (@available(iOS 12.0, *)) {
static Class portalViewClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
portalViewClass = NSClassFromString([@[@"_", @"UI", @"Portal", @"View"] componentsJoinedByString:@""]);
});
if (!portalViewClass) {
return nil;
}
UIView<UIKitPortalViewProtocol> *view = [[portalViewClass alloc] init];
if (!view) {
return nil;
}
if (@available(iOS 14.0, *)) {
view.forwardsClientHitTestingToSourceView = false;
}
view.matchesPosition = matchPosition;
view.matchesTransform = matchPosition;
view.matchesAlpha = false;
if (@available(iOS 14.0, *)) {
view.allowsHitTesting = false;
}
return view;
} else {
static Class portalViewClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
portalViewClass = NSClassFromString([@[@"_", @"UI", @"Portal", @"View"] componentsJoinedByString:@""]);
});
if (!portalViewClass) {
return nil;
}
UIView<UIKitPortalViewProtocol> *view = [[portalViewClass alloc] init];
if (!view) {
return nil;
}
if (@available(iOS 14.0, *)) {
view.forwardsClientHitTestingToSourceView = false;
}
view.matchesPosition = matchPosition;
view.matchesTransform = matchPosition;
view.matchesAlpha = false;
if (@available(iOS 14.0, *)) {
view.allowsHitTesting = false;
}
return view;
}
bool isViewPortalView(UIView * _Nonnull view) {
static Class portalViewClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
portalViewClass = NSClassFromString([@[@"_", @"UI", @"Portal", @"View"] componentsJoinedByString:@""]);
});
if ([view isKindOfClass:portalViewClass]) {
return true;
} else {
return false;
}
}
UIView * _Nullable getPortalViewSourceView(UIView * _Nonnull portalView) {
if (!isViewPortalView(portalView)) {
return nil;
}
UIView<UIKitPortalViewProtocol> *view = (UIView<UIKitPortalViewProtocol> *)portalView;
return view.sourceView;
}
@protocol GraphicsFilterProtocol <NSObject>