diff --git a/submodules/ComponentFlow/BUILD b/submodules/ComponentFlow/BUILD index 6c7d864ad5..92f3ebdd2a 100644 --- a/submodules/ComponentFlow/BUILD +++ b/submodules/ComponentFlow/BUILD @@ -10,6 +10,7 @@ swift_library( "-warnings-as-errors", ], deps = [ + "//submodules/Display" ], visibility = [ "//visibility:public", diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 18860abb54..a6679fc7ba 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Display #if targetEnvironment(simulator) @_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float @@ -15,105 +16,31 @@ private extension UIView { } } -@objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { - private let keyPath: String? - var completion: ((Bool) -> Void)? - - init(animation: CAAnimation, completion: ((Bool) -> Void)?) { - if let animation = animation as? CABasicAnimation { - self.keyPath = animation.keyPath - } else { - self.keyPath = nil - } - self.completion = completion - - super.init() - } - - @objc func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { - if let anim = anim as? CABasicAnimation { - if anim.keyPath != self.keyPath { - return - } - } - if let completion = self.completion { - completion(flag) - } - } -} - -private func makeSpringAnimation(keyPath: String) -> CASpringAnimation { - let springAnimation = CASpringAnimation(keyPath: keyPath) - springAnimation.mass = 3.0; - springAnimation.stiffness = 1000.0 - springAnimation.damping = 500.0 - springAnimation.duration = 0.5 - springAnimation.timingFunction = CAMediaTimingFunction(name: .linear) - return springAnimation -} - private extension CALayer { - func makeAnimation(from: AnyObject?, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) -> CAAnimation { + func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) { + let timingFunction: String + let mediaTimingFunction: CAMediaTimingFunction? switch curve { case .spring: - let animation = makeSpringAnimation(keyPath: keyPath) - animation.fromValue = from - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = .forwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) - } - - let k = Float(UIView.animationDurationFactor) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - if !delay.isZero { - animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor - animation.fillMode = .both - } - - return animation + timingFunction = kCAMediaTimingFunctionSpring + mediaTimingFunction = nil default: - let k = Float(UIView.animationDurationFactor) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - let animation = CABasicAnimation(keyPath: keyPath) - if let from = from { - animation.fromValue = from - } - animation.toValue = to - animation.duration = duration - animation.timingFunction = curve.asTimingFunction() - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = .both - animation.speed = speed - animation.isAdditive = additive - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) - } - - if !delay.isZero { - animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor - animation.fillMode = .both - } - - return animation + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + mediaTimingFunction = curve.asTimingFunction() } - } - - func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) { - let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, duration: duration, delay: delay, curve: curve, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) - self.add(animation, forKey: additive ? nil : keyPath) + + self.animate( + from: from, + to: to, + keyPath: keyPath, + timingFunction: timingFunction, + duration: duration, + delay: delay, + mediaTimingFunction: mediaTimingFunction, + removeOnCompletion: removeOnCompletion, + additive: additive, + completion: completion + ) } } diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 2da1a6b38d..237cc80892 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -62,6 +62,14 @@ private final class FrameRangeContext { if self.animationCount != 0 { if self.displayLink == nil { let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent)) + + if #available(iOS 15.0, *) { + let maxFps = Float(UIScreen.main.maximumFramesPerSecond) + if maxFps > 61.0 { + displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) + } + } + self.displayLink = displayLink displayLink.add(to: .main, forMode: .common) displayLink.isPaused = false @@ -95,9 +103,18 @@ public extension CAAnimation { private func adjustFrameRate(animation: CAAnimation) { if #available(iOS 15.0, *) { + if let animation = animation as? CABasicAnimation { + if animation.keyPath == "opacity" { + return + } + } let maxFps = Float(UIScreen.main.maximumFramesPerSecond) if maxFps > 61.0 { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) + #if DEBUG + //let _ = frameRangeContext.add() + #endif + + animation.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: maxFps, preferred: maxFps) } } } @@ -138,32 +155,64 @@ public extension CALayer { return animation } else if timingFunction == kCAMediaTimingFunctionSpring { - let animation = makeSpringAnimation(keyPath) - animation.fromValue = from - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = .forwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) + if duration == 0.5 { + let animation = makeSpringAnimation(keyPath) + animation.fromValue = from + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) + } + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + if !delay.isZero { + animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor() + animation.fillMode = .both + } + + adjustFrameRate(animation: animation) + + return animation + } else { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + + animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.380, 0.700, 0.125, 1.000) + + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + animation.speed = speed + animation.isAdditive = additive + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) + } + + if !delay.isZero { + animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor() + animation.fillMode = .both + } + + adjustFrameRate(animation: animation) + + return animation } - - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - if !delay.isZero { - animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor() - animation.fillMode = .both - } - - adjustFrameRate(animation: animation) - - return animation } else { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 @@ -178,7 +227,13 @@ public extension CALayer { if let mediaTimingFunction = mediaTimingFunction { animation.timingFunction = mediaTimingFunction } else { - animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction)) + switch timingFunction { + case CAMediaTimingFunctionName.linear.rawValue, CAMediaTimingFunctionName.easeIn.rawValue, CAMediaTimingFunctionName.easeOut.rawValue, CAMediaTimingFunctionName.easeInEaseOut.rawValue, CAMediaTimingFunctionName.default.rawValue: + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction)) + default: + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + } + } animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = .forwards diff --git a/submodules/PeerOnlineMarkerNode/Sources/PeerOnlineMarkerNode.swift b/submodules/PeerOnlineMarkerNode/Sources/PeerOnlineMarkerNode.swift index 859550367e..52bf64fe0d 100644 --- a/submodules/PeerOnlineMarkerNode/Sources/PeerOnlineMarkerNode.swift +++ b/submodules/PeerOnlineMarkerNode/Sources/PeerOnlineMarkerNode.swift @@ -161,7 +161,7 @@ public final class PeerOnlineMarkerNode: ASDisplayNode { } if animated { - let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.0 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0) + let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.001 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0) let targetScale: CGFloat = online ? 1.0 : 0.0 if initialScale != targetScale { strongSelf.iconNode.isHidden = false diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index f482bd96fa..32f0a471f8 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -209,8 +209,64 @@ private func extractAccountManagerState(records: AccountRecordsView 61.0 { + displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) + } + } + + self.displayLink = displayLink + displayLink.add(to: .main, forMode: .common) + displayLink.isPaused = false + } + } else if let displayLink = self.displayLink { + self.displayLink = nil + displayLink.invalidate() + } + } +} + @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate { @objc var window: UIWindow? + private var animationSupportContext: AnimationSupportContext? var nativeWindow: (UIWindow & WindowHost)? var mainWindow: Window1! private var dataImportSplash: LegacyDataImportSplash? @@ -306,6 +362,9 @@ private func extractAccountManagerState(records: AccountRecordsView