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
|
//
|
||
6 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
|
||
6 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
|
}
|
||
|
|
||
6 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]
|
||
|
}
|
||
|
|
||
6 years ago
|
private var isAnimating = false
|
||
|
|
||
7 years ago
|
@objc
|
||
|
public func startAnimation() {
|
||
6 years ago
|
isAnimating = true
|
||
|
|
||
7 years ago
|
for dot in dots() {
|
||
|
dot.startAnimation()
|
||
|
}
|
||
7 years ago
|
}
|
||
|
|
||
|
@objc
|
||
|
public func stopAnimation() {
|
||
6 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
|
||
6 years ago
|
let dotColor = baseColor.withAlphaComponent(CGFloatLerp(0.4, 1.0, CGFloatClamp01(progress)))
|
||
7 years ago
|
colorValues.append(dotColor.cgColor)
|
||
6 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
|
}
|
||
|
}
|
||
|
}
|