diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 4ccff7b6a4..c527ad0c90 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1027,6 +1027,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture transition = .animated(duration: duration, curve: .spring) case let .Default(duration): transition = .animated(duration: max(updateSizeAndInsets.duration, duration ?? 0.3), curve: .easeInOut) + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + transition = .animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)) } } } else if let scrollToItem = scrollToItem { @@ -1040,6 +1042,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else { transition = .animated(duration: duration ?? 0.3, curve: .easeInOut) } + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + transition = .animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)) } } } @@ -2665,6 +2669,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture scrollToItemTransition = .animated(duration: duration, curve: .spring) case let .Default(duration): scrollToItemTransition = .animated(duration: duration ?? 0.3, curve: .easeInOut) + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + scrollToItemTransition = .animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)) } } @@ -2751,6 +2757,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture updateSizeAndInsetsTransition = .animated(duration: duration, curve: .spring) case let .Default(duration): updateSizeAndInsetsTransition = .animated(duration: duration ?? 0.3, curve: .easeInOut) + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + updateSizeAndInsetsTransition = .animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)) } } @@ -2804,6 +2812,20 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } animationDuration = duration + springAnimation.isAdditive = true + animation = springAnimation + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + headerNodesTransition = (.animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)), false, -completeOffset) + animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) + let springAnimation = CABasicAnimation(keyPath: "sublayerTransform") + springAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y) + springAnimation.duration = duration * UIView.animationDurationFactor() + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + + animationDuration = duration + springAnimation.isAdditive = true animation = springAnimation case let .Default(duration): @@ -2993,6 +3015,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture headerNodesTransition = (.animated(duration: duration, curve: .spring), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) case let .Default(duration): headerNodesTransition = (.animated(duration: duration ?? 0.3, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + headerNodesTransition = (.animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) } for (_, headerNode) in self.itemHeaderNodes { previousItemHeaderNodes.append(headerNode) @@ -3065,6 +3089,43 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture animation = springAnimation reverseAnimation = reverseSpringAnimation + /* + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + headerNodesTransition = (.animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)), false, -completeOffset) + animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) + let springAnimation = CABasicAnimation(keyPath: "sublayerTransform") + springAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y) + springAnimation.duration = duration * UIView.animationDurationFactor() + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + + animationDuration = duration + + springAnimation.isAdditive = true + animation = springAnimation + */ + case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): + animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) + animationDuration = duration + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y) + basicAnimation.duration = duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + + let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y) + reverseBasicAnimation.duration = duration * UIView.animationDurationFactor() + reverseBasicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0)) + reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + reverseBasicAnimation.isRemovedOnCompletion = true + reverseBasicAnimation.isAdditive = true + + animation = basicAnimation + reverseAnimation = reverseBasicAnimation case let .Default(duration): if let duration = duration { animationCurve = .easeInOut diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index ccc1344711..56d0e73bec 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -22,6 +22,7 @@ public enum ListViewScrollToItemDirectionHint { public enum ListViewAnimationCurve { case Spring(duration: Double) case Default(duration: Double?) + case Custom(duration: Double, Float, Float, Float, Float) } public struct ListViewScrollToItem { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f5b9533f3e..00b5a85e8f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4482,7 +4482,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { isScheduledMessages = false } - strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.4, curve: .spring), listViewTransaction: { updateSizeAndInsets, _, _, _ in + strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.5, curve: .custom(0.33, 0.0, 0.0, 1.0)), listViewTransaction: { updateSizeAndInsets, _, _, _ in var options = transition.options let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) @@ -4508,9 +4508,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var scrollToItem: ListViewScrollToItem? if isScheduledMessages, let insertedIndex = insertedIndex { - scrollToItem = ListViewScrollToItem(index: insertedIndex, position: .visible, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: insertedIndex, position: .visible, animated: true, curve: .Custom(duration: 0.5, 0.33, 0.0, 0.0, 1.0), directionHint: .Down) } else if transition.historyView.originalView.laterId == nil { - scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up) + scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Custom(duration: 0.5, 0.33, 0.0, 0.0, 1.0), directionHint: .Up) } var stationaryItemRange: (Int, Int)? diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 6f6efe5bdd..5e448be1f6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -86,10 +86,10 @@ final class ChatMessageTransitionNode: ASDisplayNode { self.itemNode.cancelInsertionAnimations() - let duration: Double = 0.4 + let duration: Double = 0.5 let delay: Double = 0.0 - let transition: ContainedViewLayoutTransition = .animated(duration: duration * 0.8, curve: .spring) + let transition: ContainedViewLayoutTransition = .animated(duration: duration * 0.5, curve: .custom(0.33, 0.0, 0.0, 1.0)) if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition) @@ -100,15 +100,15 @@ final class ChatMessageTransitionNode: ASDisplayNode { self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY) self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), to: CGPoint(), duration: duration, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), to: CGPoint(), duration: duration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return } strongSelf.endAnimation() }) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), .spring, duration * 0.8) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), .spring, duration) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), to: CGPoint(), duration: duration * 0.8, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), .custom(0.33, 0.0, 0.0, 1.0), duration * 0.5) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), .custom(0.33, 0.0, 0.0, 1.0), duration) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), to: CGPoint(), duration: duration * 0.5, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) } }