mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 02:45:58 +00:00
Bounce experiment
This commit is contained in:
parent
1ff56084fe
commit
4b04a6c69e
@ -322,8 +322,9 @@ public extension CALayer {
|
||||
return animation
|
||||
}
|
||||
|
||||
func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, stiffness: CGFloat = 900.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping)
|
||||
animation.stiffness = stiffness
|
||||
animation.fromValue = from
|
||||
animation.toValue = to
|
||||
animation.isRemovedOnCompletion = removeOnCompletion
|
||||
|
||||
@ -138,6 +138,10 @@ private extension CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
private func bounceParameters(duration: Double) -> (duration: Double, damping: CGFloat, stiffness: CGFloat) {
|
||||
return (duration: duration * 1.25, damping: 88.0, stiffness: 750.0)
|
||||
}
|
||||
|
||||
public extension ContainedViewLayoutTransition {
|
||||
func animation() -> CABasicAnimation? {
|
||||
switch self {
|
||||
@ -469,6 +473,106 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updatePositionSpring(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.position.equalTo(position) {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "position")
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.center = position
|
||||
} else {
|
||||
layer.position = position
|
||||
}
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let _ = curve
|
||||
let previousPosition = layer.position
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.center = position
|
||||
} else {
|
||||
layer.position = position
|
||||
}
|
||||
let params = bounceParameters(duration: duration)
|
||||
layer.animateSpring(from: NSValue(cgPoint: previousPosition), to: NSValue(cgPoint: position), keyPath: "position", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { flag in
|
||||
if let completion {
|
||||
completion(flag)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateScaleSpring(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
let t = layer.transform
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
if abs(CGFloat(currentScale) - scale) <= CGFloat(Float.ulpOfOne) {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "transform.scale")
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.transform = CGAffineTransformMakeScale(scale, scale)
|
||||
} else {
|
||||
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
}
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let _ = curve
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.transform = CGAffineTransformMakeScale(scale, scale)
|
||||
} else {
|
||||
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
}
|
||||
let params = bounceParameters(duration: duration)
|
||||
layer.animateSpring(from: currentScale as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { flag in
|
||||
if let completion {
|
||||
completion(flag)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateBoundsSpring(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.bounds.equalTo(bounds) {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.removeAnimation(forKey: "bounds")
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.bounds = bounds
|
||||
} else {
|
||||
layer.bounds = bounds
|
||||
}
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let _ = curve
|
||||
let previousBounds = layer.bounds
|
||||
if let view = layer.delegate as? UIView {
|
||||
view.bounds = bounds
|
||||
} else {
|
||||
layer.bounds = bounds
|
||||
}
|
||||
let params = bounceParameters(duration: duration)
|
||||
layer.animateSpring(from: NSValue(cgRect: previousBounds), to: NSValue(cgRect: bounds), keyPath: "bounds", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateAnchorPoint(layer: CALayer, anchorPoint: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.anchorPoint.equalTo(anchorPoint) && !force {
|
||||
completion?(true)
|
||||
|
||||
@ -12,7 +12,7 @@ public func makeSpringAnimation(_ keyPath: String, duration: Double) -> CABasicA
|
||||
return makeSpringAnimationImpl(keyPath, duration)
|
||||
}
|
||||
|
||||
public func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation {
|
||||
public func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CASpringAnimation {
|
||||
return makeSpringBounceAnimationImpl(keyPath, initialVelocity, damping)
|
||||
}
|
||||
|
||||
|
||||
@ -288,6 +288,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
private var rightSlowModeInset: CGFloat = 0.0
|
||||
private var currentTextInputBackgroundWidthOffset: CGFloat = 0.0
|
||||
|
||||
private var enableBounceAnimations: Bool = false
|
||||
|
||||
public var displayAttachmentMenu: () -> Void = { }
|
||||
public var sendMessage: () -> Void = { }
|
||||
public var paste: (ChatTextInputPanelPasteData) -> Void = { _ in }
|
||||
@ -320,6 +322,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var currentInputHasText: Bool = false
|
||||
|
||||
public var inputTextState: ChatTextInputState {
|
||||
if let textInputNode = self.textInputNode {
|
||||
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
|
||||
@ -636,6 +640,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
self.context = context
|
||||
|
||||
self.enableBounceAnimations = true
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_input_bounce"] != nil {
|
||||
self.enableBounceAnimations = false
|
||||
}
|
||||
|
||||
self.addSubnode(self.clippingNode)
|
||||
|
||||
self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in
|
||||
@ -1416,6 +1425,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
|
||||
inputHasText = true
|
||||
}
|
||||
let inputHadText = self.currentInputHasText
|
||||
self.currentInputHasText = inputHasText
|
||||
|
||||
var useBounceAnimation = inputHasText && !inputHadText
|
||||
if accessoryPanel != nil || self.accessoryPanel != nil {
|
||||
useBounceAnimation = false
|
||||
}
|
||||
if !self.enableBounceAnimations {
|
||||
useBounceAnimation = false
|
||||
}
|
||||
|
||||
var hasMenuButton = false
|
||||
var menuButtonExpanded = false
|
||||
@ -1957,7 +1976,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
menuButtonTitleTransition.updateFrame(node: self.menuButtonTextNode, frame: CGRect(origin: CGPoint(x: 16.0, y: 11.0), size: menuTextSize))
|
||||
transition.updateAlpha(node: self.menuButtonTextNode, alpha: menuButtonExpanded ? 1.0 : 0.0)
|
||||
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: 5.0, y: isSendAsButton ? 5.0 : (5.0 - UIScreenPixel), width: 30.0, height: 30.0))
|
||||
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: 7.0, y: 7.0, width: 26.0, height: 26.0))
|
||||
|
||||
transition.updateFrame(node: self.sendAsAvatarButtonNode, frame: menuButtonFrame)
|
||||
transition.updateFrame(node: self.sendAsAvatarContainerNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
||||
@ -2384,7 +2403,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
transition.updateFrame(view: self.accessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
|
||||
transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
|
||||
|
||||
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
|
||||
var textInputContainerBackgroundTransition = ComponentTransition(transition)
|
||||
if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve {
|
||||
textInputContainerBackgroundTransition = textInputContainerBackgroundTransition.withUserData(GlassBackgroundView.TransitionFlagBounce())
|
||||
}
|
||||
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: textInputContainerBackgroundTransition)
|
||||
|
||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame)
|
||||
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
||||
@ -2626,13 +2649,22 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
|
||||
var sendActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX - sendActionButtonsSize.width, y: textInputContainerBackgroundFrame.maxY - sendActionButtonsSize.height), size: sendActionButtonsSize)
|
||||
|
||||
let sendActionsScale: CGFloat
|
||||
if inputHasText || hasMediaDraft || hasForward {
|
||||
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: 1.0, y: 1.0))
|
||||
sendActionsScale = 1.0
|
||||
} else {
|
||||
sendActionsScale = 0.001
|
||||
sendActionButtonsFrame.origin.x += (sendActionButtonsSize.width - 3.0 * 2.0) * 0.5 - 3.0
|
||||
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: 0.001, y: 0.001))
|
||||
}
|
||||
transition.updatePosition(node: self.sendActionButtons, position: sendActionButtonsFrame.center)
|
||||
|
||||
if useBounceAnimation, case let .animated(duration, curve) = transition, case .spring = curve {
|
||||
ContainedViewLayoutTransition.animated(duration: duration, curve: curve).updateScaleSpring(layer: self.sendActionButtons.layer, scale: sendActionsScale)
|
||||
ContainedViewLayoutTransition.animated(duration: duration, curve: curve).updatePositionSpring(layer: self.sendActionButtons.layer, position: sendActionButtonsFrame.center)
|
||||
} else {
|
||||
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: sendActionsScale, y: sendActionsScale))
|
||||
transition.updatePosition(node: self.sendActionButtons, position: sendActionButtonsFrame.center)
|
||||
}
|
||||
transition.updateBounds(node: self.sendActionButtons, bounds: CGRect(origin: CGPoint(), size: sendActionButtonsFrame.size))
|
||||
if let (rect, containerSize) = self.absoluteRect {
|
||||
self.sendActionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + sendActionButtonsFrame.origin.x, y: rect.origin.y + sendActionButtonsFrame.origin.y, width: sendActionButtonsFrame.width, height: sendActionButtonsFrame.height), within: containerSize, transition: transition)
|
||||
@ -3806,7 +3838,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
if self.sendActionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero {
|
||||
alphaTransition.updateAlpha(node: self.sendActionButtons.sendContainerNode, alpha: 1.0)
|
||||
blurTransitionIn.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: sendButtonBlurOut, toRadius: 0.0)
|
||||
transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -18.0, y: 14.0))
|
||||
transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -22.0, y: 18.0))
|
||||
if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode {
|
||||
alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 1.0)
|
||||
}
|
||||
|
||||
@ -48,6 +48,11 @@ private final class ContentContainer: UIView {
|
||||
}
|
||||
|
||||
public class GlassBackgroundView: UIView {
|
||||
public final class TransitionFlagBounce {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContentView: UIView {
|
||||
var tintMask: UIView { get }
|
||||
}
|
||||
@ -377,23 +382,27 @@ public class GlassBackgroundView: UIView {
|
||||
|
||||
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) {
|
||||
if let nativeContainerView = self.nativeContainerView, let nativeView = self.nativeView, nativeView.bounds.size != size {
|
||||
//let previousFrame = nativeView.frame
|
||||
|
||||
if transition.animation.isImmediate {
|
||||
nativeView.layer.cornerRadius = cornerRadius
|
||||
nativeView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
nativeContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
|
||||
} else {
|
||||
/*transition.containedViewLayoutTransition.animateView {
|
||||
nativeView.layer.cornerRadius = cornerRadius
|
||||
nativeView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
nativeContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
|
||||
}*/
|
||||
nativeView.layer.cornerRadius = cornerRadius
|
||||
transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(view: nativeContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0))))
|
||||
|
||||
//nativeView.layer.animateFrame(from: previousFrame, to: CGRect(origin: CGPoint(), size: size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
let nativeFrame = CGRect(origin: CGPoint(), size: size)
|
||||
let nativeContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
|
||||
|
||||
if transition.userData(TransitionFlagBounce.self) != nil {
|
||||
transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeView.layer, position: nativeFrame.center)
|
||||
transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeView.layer, bounds: CGRect(origin: CGPoint(), size: nativeFrame.size))
|
||||
|
||||
transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeContainerView.layer, position: nativeContainerFrame.center)
|
||||
transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeContainerView.layer, bounds: CGRect(origin: CGPoint(), size: nativeContainerFrame.size))
|
||||
} else {
|
||||
transition.setFrame(view: nativeView, frame: nativeFrame)
|
||||
transition.setFrame(view: nativeContainerView, frame: nativeContainerFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let backgroundNode = self.backgroundNode {
|
||||
|
||||
@ -5,7 +5,7 @@ double animationDurationFactorImpl();
|
||||
|
||||
CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath, double duration);
|
||||
CABasicAnimation * _Nonnull make26SpringAnimationImpl(NSString * _Nonnull keyPath, double duration);
|
||||
CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
|
||||
CASpringAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
|
||||
CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloat t);
|
||||
|
||||
UIBlurEffect * _Nonnull makeCustomZoomBlurEffectImpl(bool isLight);
|
||||
|
||||
@ -83,7 +83,7 @@ CABasicAnimation * _Nonnull make26SpringAnimationImpl(NSString * _Nonnull keyPat
|
||||
return springAnimation;
|
||||
}
|
||||
|
||||
CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
|
||||
CASpringAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
|
||||
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath];
|
||||
springAnimation.mass = 5.0f;
|
||||
springAnimation.stiffness = 900.0f;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user