| 
						
						
						
					 | 
					 | 
					@ -1,21 +1,22 @@
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					import Accelerate
 | 
					 | 
					 | 
					 | 
					import Accelerate
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					@objc(LKVoiceMessageViewDelegate)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					protocol VoiceMessageViewDelegate {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    func showLoader()
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    func hideLoader()
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					}
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					@objc(LKVoiceMessageView)
 | 
					 | 
					 | 
					 | 
					@objc(LKVoiceMessageView)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					final class VoiceMessageView : UIView {
 | 
					 | 
					 | 
					 | 
					final class VoiceMessageView : UIView {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private let voiceMessage: TSAttachment
 | 
					 | 
					 | 
					 | 
					    private let voiceMessage: TSAttachment
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private let isOutgoing: Bool
 | 
					 | 
					 | 
					 | 
					    private let isOutgoing: Bool
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private var isAnimating = false
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private var volumeSamples: [Float] = [] { didSet { updateShapeLayers() } }
 | 
					 | 
					 | 
					 | 
					    private var volumeSamples: [Float] = [] { didSet { updateShapeLayers() } }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private var progress: CGFloat = 0
 | 
					 | 
					 | 
					 | 
					    private var progress: CGFloat = 0
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    @objc var delegate: VoiceMessageViewDelegate?
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    @objc var duration: Int = 0 { didSet { updateDurationLabel() } }
 | 
					 | 
					 | 
					 | 
					    @objc var duration: Int = 0 { didSet { updateDurationLabel() } }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    // MARK: Components
 | 
					 | 
					 | 
					 | 
					    // MARK: Components
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private lazy var loader: UIView = {
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        let result = UIView()
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        result.backgroundColor = Colors.text.withAlphaComponent(0.2)
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        return result
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }()
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private lazy var durationLabel: UILabel = {
 | 
					 | 
					 | 
					 | 
					    private lazy var durationLabel: UILabel = {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        let result = UILabel()
 | 
					 | 
					 | 
					 | 
					        let result = UILabel()
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        result.textColor = Colors.text
 | 
					 | 
					 | 
					 | 
					        result.textColor = Colors.text
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -47,7 +48,6 @@ final class VoiceMessageView : UIView {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        self.voiceMessage = voiceMessage
 | 
					 | 
					 | 
					 | 
					        self.voiceMessage = voiceMessage
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        self.isOutgoing = isOutgoing
 | 
					 | 
					 | 
					 | 
					        self.isOutgoing = isOutgoing
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        super.init(frame: CGRect.zero)
 | 
					 | 
					 | 
					 | 
					        super.init(frame: CGRect.zero)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        initialize()
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					    }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    override init(frame: CGRect) {
 | 
					 | 
					 | 
					 | 
					    override init(frame: CGRect) {
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -58,17 +58,16 @@ final class VoiceMessageView : UIView {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        preconditionFailure("Use init(voiceMessage:associatedWith:) instead.")
 | 
					 | 
					 | 
					 | 
					        preconditionFailure("Use init(voiceMessage:associatedWith:) instead.")
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					    }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private func initialize() {
 | 
					 | 
					 | 
					 | 
					    @objc func initialize() {
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        setUpViewHierarchy()
 | 
					 | 
					 | 
					 | 
					        setUpViewHierarchy()
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        if voiceMessage.isDownloaded {
 | 
					 | 
					 | 
					 | 
					        if voiceMessage.isDownloaded {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            loader.alpha = 0
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            guard let url = (voiceMessage as? TSAttachmentStream)?.originalMediaURL else {
 | 
					 | 
					 | 
					 | 
					            guard let url = (voiceMessage as? TSAttachmentStream)?.originalMediaURL else {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                return print("[Loki] Couldn't get URL for voice message.")
 | 
					 | 
					 | 
					 | 
					                return print("[Loki] Couldn't get URL for voice message.")
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            }
 | 
					 | 
					 | 
					 | 
					            }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            let targetSampleCount = 48
 | 
					 | 
					 | 
					 | 
					            let targetSampleCount = 48
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            if let cachedVolumeSamples = Storage.getVolumeSamples(for: voiceMessage.uniqueId!), cachedVolumeSamples.count == targetSampleCount {
 | 
					 | 
					 | 
					 | 
					            if let cachedVolumeSamples = Storage.getVolumeSamples(for: voiceMessage.uniqueId!), cachedVolumeSamples.count == targetSampleCount {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                self.volumeSamples = cachedVolumeSamples
 | 
					 | 
					 | 
					 | 
					                self.volumeSamples = cachedVolumeSamples
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                self.stopAnimating()
 | 
					 | 
					 | 
					 | 
					                self.delegate?.hideLoader()
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            } else {
 | 
					 | 
					 | 
					 | 
					            } else {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                let voiceMessageID = voiceMessage.uniqueId!
 | 
					 | 
					 | 
					 | 
					                let voiceMessageID = voiceMessage.uniqueId!
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                AudioUtilities.getVolumeSamples(for: url, targetSampleCount: targetSampleCount).done(on: DispatchQueue.main) { [weak self] volumeSamples in
 | 
					 | 
					 | 
					 | 
					                AudioUtilities.getVolumeSamples(for: url, targetSampleCount: targetSampleCount).done(on: DispatchQueue.main) { [weak self] volumeSamples in
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -78,22 +77,20 @@ final class VoiceMessageView : UIView {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                        Storage.setVolumeSamples(for: voiceMessageID, to: volumeSamples, using: transaction)
 | 
					 | 
					 | 
					 | 
					                        Storage.setVolumeSamples(for: voiceMessageID, to: volumeSamples, using: transaction)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                    }
 | 
					 | 
					 | 
					 | 
					                    }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                    self.durationLabel.alpha = 1
 | 
					 | 
					 | 
					 | 
					                    self.durationLabel.alpha = 1
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                    self.stopAnimating()
 | 
					 | 
					 | 
					 | 
					                    self.delegate?.hideLoader()
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                }.catch(on: DispatchQueue.main) { error in
 | 
					 | 
					 | 
					 | 
					                }.catch(on: DispatchQueue.main) { error in
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                    print("[Loki] Couldn't sample audio file due to error: \(error).")
 | 
					 | 
					 | 
					 | 
					                    print("[Loki] Couldn't sample audio file due to error: \(error).")
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					                }
 | 
					 | 
					 | 
					 | 
					                }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            }
 | 
					 | 
					 | 
					 | 
					            }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        } else {
 | 
					 | 
					 | 
					 | 
					        } else {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            durationLabel.alpha = 0
 | 
					 | 
					 | 
					 | 
					            durationLabel.alpha = 0
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            showLoader()
 | 
					 | 
					 | 
					 | 
					            delegate?.showLoader()
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        }
 | 
					 | 
					 | 
					 | 
					        }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					    }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private func setUpViewHierarchy() {
 | 
					 | 
					 | 
					 | 
					    private func setUpViewHierarchy() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        set(.width, to: 200)
 | 
					 | 
					 | 
					 | 
					        set(.width, to: 200)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        set(.height, to: VoiceMessageView.contentHeight)
 | 
					 | 
					 | 
					 | 
					        set(.height, to: VoiceMessageView.contentHeight)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        addSubview(loader)
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        loader.pin(to: self)
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        layer.insertSublayer(backgroundShapeLayer, at: 0)
 | 
					 | 
					 | 
					 | 
					        layer.insertSublayer(backgroundShapeLayer, at: 0)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        layer.insertSublayer(foregroundShapeLayer, at: 1)
 | 
					 | 
					 | 
					 | 
					        layer.insertSublayer(foregroundShapeLayer, at: 1)
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        addSubview(durationLabel)
 | 
					 | 
					 | 
					 | 
					        addSubview(durationLabel)
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -102,27 +99,6 @@ final class VoiceMessageView : UIView {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					    }
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    // MARK: UI & Updating
 | 
					 | 
					 | 
					 | 
					    // MARK: UI & Updating
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private func showLoader() {
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        isAnimating = true
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        loader.alpha = 1
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        animateLoader()
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private func animateLoader() {
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        loader.frame = CGRect(x: 0, y: 0, width: 0, height: VoiceMessageView.contentHeight)
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        UIView.animate(withDuration: 2) { [weak self] in
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            self?.loader.frame = CGRect(x: 0, y: 0, width: 200, height: VoiceMessageView.contentHeight)
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        } completion: { [weak self] _ in
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            guard let self = self else { return }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            if self.isAnimating { self.animateLoader() }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    private func stopAnimating() {
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        isAnimating = false
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        loader.alpha = 0
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    }
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    override func layoutSubviews() {
 | 
					 | 
					 | 
					 | 
					    override func layoutSubviews() {
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        super.layoutSubviews()
 | 
					 | 
					 | 
					 | 
					        super.layoutSubviews()
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        updateShapeLayers()
 | 
					 | 
					 | 
					 | 
					        updateShapeLayers()
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					 | 
					
 
 |