|
|
|
@ -5,6 +5,7 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
private lazy var slideToCancelLabelCenterHorizontalConstraint = slideToCancelLabel.center(.horizontal, in: self)
|
|
|
|
|
private lazy var pulseViewWidthConstraint = pulseView.set(.width, to: VoiceMessageRecordingView.circleSize)
|
|
|
|
|
private lazy var pulseViewHeightConstraint = pulseView.set(.height, to: VoiceMessageRecordingView.circleSize)
|
|
|
|
|
private lazy var lockViewBottomConstraint = lockView.pin(.bottom, to: .top, of: self, withInset: Values.mediumSpacing)
|
|
|
|
|
private let recordingStartDate = Date()
|
|
|
|
|
private var recordingTimer: Timer?
|
|
|
|
|
|
|
|
|
@ -61,6 +62,8 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
return result
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
private lazy var lockView = LockView()
|
|
|
|
|
|
|
|
|
|
// MARK: Settings
|
|
|
|
|
private static let circleSize: CGFloat = 96
|
|
|
|
|
private static let pulseSize: CGFloat = 24
|
|
|
|
@ -117,7 +120,7 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
pulseView.center(in: circleView)
|
|
|
|
|
// Slide to cancel stack view
|
|
|
|
|
let chevronSize = VoiceMessageRecordingView.chevronSize
|
|
|
|
|
let chevronColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
|
|
|
|
let chevronColor = (isLightMode ? UIColor.black : UIColor.white).withAlphaComponent(Values.mediumOpacity)
|
|
|
|
|
let chevronImageView = UIImageView(image: UIImage(named: "small_chevron_left")!.withTint(chevronColor))
|
|
|
|
|
chevronImageView.contentMode = .scaleAspectFit
|
|
|
|
|
chevronImageView.set(.width, to: chevronSize)
|
|
|
|
@ -134,13 +137,9 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
durationStackView.pin(.left, to: .left, of: self, withInset: Values.largeSpacing)
|
|
|
|
|
durationStackView.center(.vertical, in: iconImageView)
|
|
|
|
|
// Lock view
|
|
|
|
|
let lockView = UIView()
|
|
|
|
|
lockView.backgroundColor = .blue
|
|
|
|
|
lockView.set(.width, to: 60)
|
|
|
|
|
lockView.set(.height, to: 60)
|
|
|
|
|
addSubview(lockView)
|
|
|
|
|
lockView.pin(.bottom, to: .top, of: self, withInset: -40)
|
|
|
|
|
lockView.center(.horizontal, in: iconImageView)
|
|
|
|
|
lockViewBottomConstraint.isActive = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Updating
|
|
|
|
@ -152,30 +151,33 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
// MARK: Animation
|
|
|
|
|
func animate() {
|
|
|
|
|
layoutIfNeeded()
|
|
|
|
|
self.slideToCancelStackViewRightConstraint.isActive = false
|
|
|
|
|
self.slideToCancelLabelCenterHorizontalConstraint.isActive = true
|
|
|
|
|
UIView.animate(withDuration: 0.25, animations: {
|
|
|
|
|
slideToCancelStackViewRightConstraint.isActive = false
|
|
|
|
|
slideToCancelLabelCenterHorizontalConstraint.isActive = true
|
|
|
|
|
lockViewBottomConstraint.constant = -Values.mediumSpacing
|
|
|
|
|
UIView.animate(withDuration: 0.25, animations: { [weak self] in
|
|
|
|
|
guard let self = self else { return }
|
|
|
|
|
self.alpha = 1
|
|
|
|
|
self.layoutIfNeeded()
|
|
|
|
|
}, completion: { _ in
|
|
|
|
|
}, completion: { [weak self] _ in
|
|
|
|
|
guard let self = self else { return }
|
|
|
|
|
self.fadeOutDotView()
|
|
|
|
|
self.pulse()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func fadeOutDotView() {
|
|
|
|
|
UIView.animate(withDuration: 0.5, animations: {
|
|
|
|
|
self.dotView.alpha = 0
|
|
|
|
|
}, completion: { _ in
|
|
|
|
|
self.fadeInDotView()
|
|
|
|
|
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
|
|
|
|
self?.dotView.alpha = 0
|
|
|
|
|
}, completion: { [weak self] _ in
|
|
|
|
|
self?.fadeInDotView()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func fadeInDotView() {
|
|
|
|
|
UIView.animate(withDuration: 0.5, animations: {
|
|
|
|
|
self.dotView.alpha = 1
|
|
|
|
|
}, completion: { _ in
|
|
|
|
|
self.fadeOutDotView()
|
|
|
|
|
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
|
|
|
|
self?.dotView.alpha = 1
|
|
|
|
|
}, completion: { [weak self] _ in
|
|
|
|
|
self?.fadeOutDotView()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -186,12 +188,14 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
let expandedFrame = CGRect(center: pulseView.center, size: CGSize(width: expandedSize, height: expandedSize))
|
|
|
|
|
pulseViewWidthConstraint.constant = expandedSize
|
|
|
|
|
pulseViewHeightConstraint.constant = expandedSize
|
|
|
|
|
UIView.animate(withDuration: 1, animations: {
|
|
|
|
|
UIView.animate(withDuration: 1, animations: { [weak self] in
|
|
|
|
|
guard let self = self else { return }
|
|
|
|
|
self.layoutIfNeeded()
|
|
|
|
|
self.pulseView.frame = expandedFrame
|
|
|
|
|
self.pulseView.layer.cornerRadius = expandedSize / 2
|
|
|
|
|
self.pulseView.alpha = 0
|
|
|
|
|
}, completion: { _ in
|
|
|
|
|
}, completion: { [weak self] _ in
|
|
|
|
|
guard let self = self else { return }
|
|
|
|
|
self.pulseViewWidthConstraint.constant = collapsedSize
|
|
|
|
|
self.pulseViewHeightConstraint.constant = collapsedSize
|
|
|
|
|
self.pulseView.frame = collapsedFrame
|
|
|
|
@ -201,3 +205,65 @@ final class VoiceMessageRecordingView : UIView {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Lock View
|
|
|
|
|
extension VoiceMessageRecordingView {
|
|
|
|
|
|
|
|
|
|
fileprivate final class LockView : UIView {
|
|
|
|
|
|
|
|
|
|
private static let width: CGFloat = 44
|
|
|
|
|
private static let lockIconSize: CGFloat = 20
|
|
|
|
|
private static let chevronIconSize: CGFloat = 20
|
|
|
|
|
|
|
|
|
|
override init(frame: CGRect) {
|
|
|
|
|
super.init(frame: frame)
|
|
|
|
|
setUpViewHierarchy()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
|
super.init(coder: coder)
|
|
|
|
|
setUpViewHierarchy()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func setUpViewHierarchy() {
|
|
|
|
|
let iconTint: UIColor = isLightMode ? .black : .white
|
|
|
|
|
// Background & blur
|
|
|
|
|
let backgroundView = UIView()
|
|
|
|
|
backgroundView.backgroundColor = isLightMode ? .white : .black
|
|
|
|
|
backgroundView.alpha = Values.lowOpacity
|
|
|
|
|
addSubview(backgroundView)
|
|
|
|
|
backgroundView.pin(to: self)
|
|
|
|
|
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
|
|
|
|
addSubview(blurView)
|
|
|
|
|
blurView.pin(to: self)
|
|
|
|
|
// Size & shape
|
|
|
|
|
let width = LockView.width
|
|
|
|
|
set(.width, to: width)
|
|
|
|
|
layer.cornerRadius = width / 2
|
|
|
|
|
layer.masksToBounds = true
|
|
|
|
|
// Border
|
|
|
|
|
layer.borderWidth = 1
|
|
|
|
|
let borderColor = (isLightMode ? UIColor.black : UIColor.white).withAlphaComponent(Values.veryLowOpacity)
|
|
|
|
|
layer.borderColor = borderColor.cgColor
|
|
|
|
|
// Lock icon
|
|
|
|
|
let lockIconImageView = UIImageView(image: UIImage(named: "ic_lock_outline")!.withTint(iconTint))
|
|
|
|
|
let lockIconSize = LockView.lockIconSize
|
|
|
|
|
lockIconImageView.set(.width, to: lockIconSize)
|
|
|
|
|
lockIconImageView.set(.height, to: lockIconSize)
|
|
|
|
|
// Chevron icon
|
|
|
|
|
let chevronIconImageView = UIImageView(image: UIImage(named: "ic_chevron_up")!.withTint(iconTint))
|
|
|
|
|
let chevronIconSize = LockView.chevronIconSize
|
|
|
|
|
chevronIconImageView.set(.width, to: chevronIconSize)
|
|
|
|
|
chevronIconImageView.set(.height, to: chevronIconSize)
|
|
|
|
|
// Stack view
|
|
|
|
|
let stackView = UIStackView(arrangedSubviews: [ lockIconImageView, chevronIconImageView ])
|
|
|
|
|
stackView.axis = .vertical
|
|
|
|
|
stackView.spacing = Values.smallSpacing
|
|
|
|
|
stackView.alignment = .center
|
|
|
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
|
|
|
stackView.layoutMargins = UIEdgeInsets(top: 12, leading: 0, bottom: 8, trailing: 0)
|
|
|
|
|
addSubview(stackView)
|
|
|
|
|
stackView.pin(to: self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|