session-ios/Session/Conversations V2/Message Cells/Content Views/VoiceMessageViewV2.swift

171 lines
6.7 KiB
Swift

import NVActivityIndicatorView
@objc(SNVoiceMessageView)
public final class VoiceMessageViewV2 : UIView {
private let viewItem: ConversationViewItem
private var isShowingSpeedUpLabel = false
@objc var progress: Int = 0 { didSet { handleProgressChanged() } }
@objc var isPlaying = false { didSet { handleIsPlayingChanged() } }
private lazy var progressViewRightConstraint = progressView.pin(.right, to: .right, of: self, withInset: -VoiceMessageViewV2.width)
private var attachment: TSAttachment? { viewItem.attachmentStream ?? viewItem.attachmentPointer }
private var duration: Int { Int(viewItem.audioDurationSeconds) }
// MARK: UI Components
private lazy var progressView: UIView = {
let result = UIView()
result.backgroundColor = UIColor.black.withAlphaComponent(0.2)
return result
}()
private lazy var toggleImageView: UIImageView = {
let result = UIImageView(image: UIImage(named: "Play"))
result.set(.width, to: 8)
result.set(.height, to: 8)
result.contentMode = .scaleAspectFit
return result
}()
private lazy var loader: NVActivityIndicatorView = {
let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
result.set(.width, to: VoiceMessageViewV2.toggleContainerSize + 2)
result.set(.height, to: VoiceMessageViewV2.toggleContainerSize + 2)
return result
}()
private lazy var countdownLabelContainer: UIView = {
let result = UIView()
result.backgroundColor = .white
result.layer.masksToBounds = true
result.set(.height, to: VoiceMessageViewV2.toggleContainerSize)
result.set(.width, to: 44)
return result
}()
private lazy var countdownLabel: UILabel = {
let result = UILabel()
result.textColor = .black
result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = "00:00"
return result
}()
private lazy var speedUpLabel: UILabel = {
let result = UILabel()
result.textColor = .black
result.font = .systemFont(ofSize: Values.smallFontSize)
result.alpha = 0
result.text = "1.5x"
result.textAlignment = .center
return result
}()
// MARK: Settings
private static let width: CGFloat = 160
private static let toggleContainerSize: CGFloat = 20
private static let inset = Values.smallSpacing
// MARK: Lifecycle
init(viewItem: ConversationViewItem) {
self.viewItem = viewItem
self.progress = Int(viewItem.audioProgressSeconds)
super.init(frame: CGRect.zero)
setUpViewHierarchy()
handleProgressChanged()
}
override init(frame: CGRect) {
preconditionFailure("Use init(viewItem:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(viewItem:) instead.")
}
private func setUpViewHierarchy() {
let toggleContainerSize = VoiceMessageViewV2.toggleContainerSize
let inset = VoiceMessageViewV2.inset
// Width & height
set(.width, to: VoiceMessageViewV2.width)
// Toggle
let toggleContainer = UIView()
toggleContainer.backgroundColor = .white
toggleContainer.set(.width, to: toggleContainerSize)
toggleContainer.set(.height, to: toggleContainerSize)
toggleContainer.addSubview(toggleImageView)
toggleImageView.center(in: toggleContainer)
toggleContainer.layer.cornerRadius = toggleContainerSize / 2
toggleContainer.layer.masksToBounds = true
// Line
let lineView = UIView()
lineView.backgroundColor = .white
lineView.set(.height, to: 1)
// Countdown label
countdownLabelContainer.addSubview(countdownLabel)
countdownLabel.center(in: countdownLabelContainer)
// Speed up label
countdownLabelContainer.addSubview(speedUpLabel)
speedUpLabel.center(in: countdownLabelContainer)
// Constraints
addSubview(progressView)
progressView.pin(.left, to: .left, of: self)
progressView.pin(.top, to: .top, of: self)
progressViewRightConstraint.isActive = true
progressView.pin(.bottom, to: .bottom, of: self)
addSubview(toggleContainer)
toggleContainer.pin(.left, to: .left, of: self, withInset: inset)
toggleContainer.pin(.top, to: .top, of: self, withInset: inset)
toggleContainer.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(lineView)
lineView.pin(.left, to: .right, of: toggleContainer)
lineView.center(.vertical, in: self)
addSubview(countdownLabelContainer)
countdownLabelContainer.pin(.left, to: .right, of: lineView)
countdownLabelContainer.pin(.right, to: .right, of: self, withInset: -inset)
countdownLabelContainer.center(.vertical, in: self)
addSubview(loader)
loader.center(in: toggleContainer)
}
// MARK: Updating
public override func layoutSubviews() {
super.layoutSubviews()
countdownLabelContainer.layer.cornerRadius = countdownLabelContainer.bounds.height / 2
}
private func handleIsPlayingChanged() {
toggleImageView.image = isPlaying ? UIImage(named: "Pause") : UIImage(named: "Play")
}
private func handleProgressChanged() {
let isDownloaded = (attachment?.isDownloaded == true)
loader.isHidden = isDownloaded
if isDownloaded { loader.stopAnimating() } else if !loader.isAnimating { loader.startAnimating() }
guard isDownloaded else { return }
countdownLabel.text = OWSFormat.formatDurationSeconds(duration - progress)
guard viewItem.audioProgressSeconds > 0 && viewItem.audioDurationSeconds > 0 else { return }
let fraction = viewItem.audioProgressSeconds / viewItem.audioDurationSeconds
progressViewRightConstraint.constant = -(VoiceMessageViewV2.width * (1 - fraction))
}
func showSpeedUpLabel() {
guard !isShowingSpeedUpLabel else { return }
isShowingSpeedUpLabel = true
UIView.animate(withDuration: 0.25) { [weak self] in
guard let self = self else { return }
self.countdownLabel.alpha = 0
self.speedUpLabel.alpha = 1
}
Timer.scheduledTimer(withTimeInterval: 1.25, repeats: false) { [weak self] _ in
UIView.animate(withDuration: 0.25, animations: {
guard let self = self else { return }
self.countdownLabel.alpha = 1
self.speedUpLabel.alpha = 0
}, completion: { _ in
self?.isShowingSpeedUpLabel = false
})
}
}
}