mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			208 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			208 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Swift
		
	
| 
								 
											7 years ago
										 
									 | 
							
								//
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								//  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@objc class TypingIndicatorView: UIStackView {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    // This represents the spacing between the dots
							 | 
						||
| 
								 | 
							
								    // _at their max size_.
							 | 
						||
| 
								 | 
							
								    private let kDotMaxHSpacing: CGFloat = 3
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public static let kMinRadiusPt: CGFloat = 6
							 | 
						||
| 
								 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public static let kMaxRadiusPt: CGFloat = 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private let dot1 = DotView(dotType: .dotType1)
							 | 
						||
| 
								 | 
							
								    private let dot2 = DotView(dotType: .dotType2)
							 | 
						||
| 
								 | 
							
								    private let dot3 = DotView(dotType: .dotType3)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @available(*, unavailable, message:"use other constructor instead.")
							 | 
						||
| 
								 | 
							
								    required init(coder aDecoder: NSCoder) {
							 | 
						||
| 
								 | 
							
								        notImplemented()
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @available(*, unavailable, message:"use other constructor instead.")
							 | 
						||
| 
								 | 
							
								    override init(frame: CGRect) {
							 | 
						||
| 
								 | 
							
								        notImplemented()
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public init() {
							 | 
						||
| 
								 | 
							
								        super.init(frame: .zero)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // init(arrangedSubviews:...) is not a designated initializer.
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        for dot in dots() {
							 | 
						||
| 
								 | 
							
								            addArrangedSubview(dot)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.axis = .horizontal
							 | 
						||
| 
								 | 
							
								        self.spacing = kDotMaxHSpacing
							 | 
						||
| 
								 | 
							
								        self.alignment = .center
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								        NotificationCenter.default.addObserver(self,
							 | 
						||
| 
								 | 
							
								                                               selector: #selector(didBecomeActive),
							 | 
						||
| 
								 | 
							
								                                               name: NSNotification.Name.OWSApplicationDidBecomeActive,
							 | 
						||
| 
								 | 
							
								                                               object: nil)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    deinit {
							 | 
						||
| 
								 | 
							
								        NotificationCenter.default.removeObserver(self)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // MARK: - Notifications
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @objc func didBecomeActive() {
							 | 
						||
| 
								 | 
							
								        AssertIsOnMainThread()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // CoreAnimation animations are stopped in the background, so ensure
							 | 
						||
| 
								 | 
							
								        // animations are restored if necessary.
							 | 
						||
| 
								 | 
							
								        if isAnimating {
							 | 
						||
| 
								 | 
							
								            startAnimation()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    // MARK: -
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public override func sizeThatFits(_ size: CGSize) -> CGSize {
							 | 
						||
| 
								 | 
							
								        return CGSize(width: TypingIndicatorView.kMaxRadiusPt * 3 + kDotMaxHSpacing * 2, height: TypingIndicatorView.kMaxRadiusPt)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    private func dots() -> [DotView] {
							 | 
						||
| 
								 | 
							
								        return [dot1, dot2, dot3]
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    private var isAnimating = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public func startAnimation() {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        isAnimating = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        for dot in dots() {
							 | 
						||
| 
								 | 
							
								            dot.startAnimation()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @objc
							 | 
						||
| 
								 | 
							
								    public func stopAnimation() {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        isAnimating = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        for dot in dots() {
							 | 
						||
| 
								 | 
							
								            dot.stopAnimation()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private enum DotType {
							 | 
						||
| 
								 | 
							
								        case dotType1
							 | 
						||
| 
								 | 
							
								        case dotType2
							 | 
						||
| 
								 | 
							
								        case dotType3
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private class DotView: UIView {
							 | 
						||
| 
								 | 
							
								        private let dotType: DotType
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        private let shapeLayer = CAShapeLayer()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @available(*, unavailable, message:"use other constructor instead.")
							 | 
						||
| 
								 | 
							
								        required init?(coder aDecoder: NSCoder) {
							 | 
						||
| 
								 | 
							
								            notImplemented()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @available(*, unavailable, message:"use other constructor instead.")
							 | 
						||
| 
								 | 
							
								        override init(frame: CGRect) {
							 | 
						||
| 
								 | 
							
								            notImplemented()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        init(dotType: DotType) {
							 | 
						||
| 
								 | 
							
								            self.dotType = dotType
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            super.init(frame: .zero)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            autoSetDimension(.width, toSize: kMaxRadiusPt)
							 | 
						||
| 
								 | 
							
								            autoSetDimension(.height, toSize: kMaxRadiusPt)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            layer.addSublayer(shapeLayer)
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        fileprivate func startAnimation() {
							 | 
						||
| 
								 | 
							
								            stopAnimation()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								            let baseColor = Colors.text
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            let timeIncrement: CFTimeInterval = 0.15
							 | 
						||
| 
								 | 
							
								            var colorValues = [CGColor]()
							 | 
						||
| 
								 | 
							
								            var pathValues = [CGPath]()
							 | 
						||
| 
								 | 
							
								            var keyTimes = [CFTimeInterval]()
							 | 
						||
| 
								 | 
							
								            var animationDuration: CFTimeInterval = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            let addDotKeyFrame = { (keyFrameTime: CFTimeInterval, progress: CGFloat) in
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								                let dotColor = baseColor.withAlphaComponent(CGFloatLerp(0.4, 1.0, CGFloatClamp01(progress)))
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								                colorValues.append(dotColor.cgColor)
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								                let radius = CGFloatLerp(TypingIndicatorView.kMinRadiusPt, TypingIndicatorView.kMaxRadiusPt, CGFloatClamp01(progress))
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								                let margin = (TypingIndicatorView.kMaxRadiusPt - radius) * 0.5
							 | 
						||
| 
								 | 
							
								                let bezierPath = UIBezierPath(ovalIn: CGRect(x: margin, y: margin, width: radius, height: radius))
							 | 
						||
| 
								 | 
							
								                pathValues.append(bezierPath.cgPath)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                keyTimes.append(keyFrameTime)
							 | 
						||
| 
								 | 
							
								                animationDuration = max(animationDuration, keyFrameTime)
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            // All animations in the group apparently need to have the same number
							 | 
						||
| 
								 | 
							
								            // of keyframes, and use the same timing.
							 | 
						||
| 
								 | 
							
								            switch dotType {
							 | 
						||
| 
								 | 
							
								            case .dotType1:
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(0 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(1 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(2 * timeIncrement, 1.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(3 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(4 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(5 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(6 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(10 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            case .dotType2:
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(0 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(1 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(2 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(3 * timeIncrement, 1.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(4 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(5 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(6 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(10 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            case .dotType3:
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(0 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(1 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(2 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(3 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(4 * timeIncrement, 1.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(5 * timeIncrement, 0.5)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(6 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                addDotKeyFrame(10 * timeIncrement, 0.0)
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            let makeAnimation: (String, [Any]) -> CAKeyframeAnimation = { (keyPath, values) in
							 | 
						||
| 
								 | 
							
								                let animation = CAKeyframeAnimation()
							 | 
						||
| 
								 | 
							
								                animation.keyPath = keyPath
							 | 
						||
| 
								 | 
							
								                animation.values = values
							 | 
						||
| 
								 | 
							
								                animation.duration = animationDuration
							 | 
						||
| 
								 | 
							
								                return animation
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            let groupAnimation = CAAnimationGroup()
							 | 
						||
| 
								 | 
							
								            groupAnimation.animations = [
							 | 
						||
| 
								 | 
							
								                makeAnimation("fillColor", colorValues),
							 | 
						||
| 
								 | 
							
								                makeAnimation("path", pathValues)
							 | 
						||
| 
								 | 
							
								            ]
							 | 
						||
| 
								 | 
							
								            groupAnimation.duration = animationDuration
							 | 
						||
| 
								 | 
							
								            groupAnimation.repeatCount = MAXFLOAT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            shapeLayer.add(groupAnimation, forKey: UUID().uuidString)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        fileprivate func stopAnimation() {
							 | 
						||
| 
								 | 
							
								            shapeLayer.removeAllAnimations()
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |