mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Dust effect improvements
This commit is contained in:
parent
e25f926342
commit
c89c1363ca
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user