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.
		
		
		
		
		
			
		
			
				
	
	
		
			216 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			216 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import NVActivityIndicatorView
 | |
| import SessionUIKit
 | |
| import SessionMessagingKit
 | |
| import SessionUtilitiesKit
 | |
| 
 | |
| public final class VoiceMessageView: UIView {
 | |
|     private static let width: CGFloat = 160
 | |
|     private static let toggleContainerSize: CGFloat = 20
 | |
|     private static let inset = Values.smallSpacing
 | |
|     
 | |
|     // MARK: - UI
 | |
|     
 | |
|     private lazy var progressViewRightConstraint = progressView.pin(.right, to: .right, of: self, withInset: -VoiceMessageView.width)
 | |
|     
 | |
|     private lazy var progressView: UIView = {
 | |
|         let result: UIView = UIView()
 | |
|         result.themeBackgroundColor = .messageBubble_overlay
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var toggleContainer: UIView = {
 | |
|         let result: UIView = UIView()
 | |
|         result.themeBackgroundColor = .backgroundSecondary
 | |
|         result.set(.width, to: VoiceMessageView.toggleContainerSize)
 | |
|         result.set(.height, to: VoiceMessageView.toggleContainerSize)
 | |
|         result.layer.masksToBounds = true
 | |
|         result.layer.cornerRadius = (VoiceMessageView.toggleContainerSize / 2)
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private lazy var toggleImageView: UIImageView = {
 | |
|         let result: UIImageView = UIImageView(
 | |
|             image: UIImage(named: "Play")?.withRenderingMode(.alwaysTemplate)
 | |
|         )
 | |
|         result.contentMode = .scaleAspectFit
 | |
|         result.themeTintColor = .textPrimary
 | |
|         result.set(.width, to: 8)
 | |
|         result.set(.height, to: 8)
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private let loader: NVActivityIndicatorView = {
 | |
|         let result: NVActivityIndicatorView = NVActivityIndicatorView(
 | |
|             frame: .zero,
 | |
|             type: .circleStrokeSpin,
 | |
|             color: .black,
 | |
|             padding: nil
 | |
|         )
 | |
|         result.set(.width, to: VoiceMessageView.toggleContainerSize + 2)
 | |
|         result.set(.height, to: VoiceMessageView.toggleContainerSize + 2)
 | |
|         
 | |
|         ThemeManager.onThemeChange(observer: result) { [weak result] theme, _ in
 | |
|             guard let textPrimary: UIColor = theme.color(for: .textPrimary) else { return }
 | |
|             
 | |
|             result?.color = textPrimary
 | |
|         }
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private lazy var countdownLabelContainer: UIView = {
 | |
|         let result: UIView = UIView()
 | |
|         result.clipsToBounds = true
 | |
|         result.themeBackgroundColor = .backgroundSecondary
 | |
|         result.set(.height, to: VoiceMessageView.toggleContainerSize)
 | |
|         result.set(.width, to: 44)
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private lazy var countdownLabel: UILabel = {
 | |
|         let result: UILabel = UILabel()
 | |
|         result.font = .systemFont(ofSize: Values.smallFontSize)
 | |
|         result.text = "0:00" // stringlint:disable
 | |
|         result.themeTextColor = .textPrimary
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private lazy var speedUpLabel: UILabel = {
 | |
|         let result: UILabel = UILabel()
 | |
|         result.font = .systemFont(ofSize: Values.smallFontSize)
 | |
|         result.text = "1.5x" // stringlint:disable
 | |
|         result.themeTextColor = .textPrimary
 | |
|         result.textAlignment = .center
 | |
|         result.alpha = 0
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     // MARK: - Lifecycle
 | |
|     
 | |
|     init() {
 | |
|         super.init(frame: CGRect.zero)
 | |
|         self.accessibilityIdentifier = "Voice message"
 | |
|         self.isAccessibilityElement = true        
 | |
|         setUpViewHierarchy()
 | |
|     }
 | |
| 
 | |
|     override init(frame: CGRect) {
 | |
|         preconditionFailure("Use init(viewItem:) instead.")
 | |
|     }
 | |
| 
 | |
|     required init?(coder: NSCoder) {
 | |
|         preconditionFailure("Use init(viewItem:) instead.")
 | |
|     }
 | |
| 
 | |
|     private func setUpViewHierarchy() {
 | |
|         let inset = VoiceMessageView.inset
 | |
|         
 | |
|         // Width & height
 | |
|         set(.width, to: VoiceMessageView.width)
 | |
|         
 | |
|         // Toggle
 | |
|         toggleContainer.addSubview(toggleImageView)
 | |
|         toggleImageView.center(in: toggleContainer)
 | |
|         
 | |
|         // Line
 | |
|         let lineView = UIView()
 | |
|         lineView.themeBackgroundColor = .backgroundSecondary
 | |
|         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)
 | |
|     }
 | |
| 
 | |
|     public override func layoutSubviews() {
 | |
|         super.layoutSubviews()
 | |
|         
 | |
|         countdownLabelContainer.layer.cornerRadius = (countdownLabelContainer.bounds.height / 2)
 | |
|     }
 | |
| 
 | |
|     // MARK: - Updating
 | |
|     
 | |
|     public func update(
 | |
|         with attachment: Attachment,
 | |
|         isPlaying: Bool,
 | |
|         progress: TimeInterval,
 | |
|         playbackRate: Double,
 | |
|         oldPlaybackRate: Double
 | |
|     ) {
 | |
|         switch attachment.state {
 | |
|             case .downloaded, .uploaded:
 | |
|                 loader.isHidden = true
 | |
|                 loader.stopAnimating()
 | |
|                 
 | |
|                 toggleImageView.image = (isPlaying ? UIImage(named: "Pause") : UIImage(named: "Play"))?
 | |
|                     .withRenderingMode(.alwaysTemplate)
 | |
|                 countdownLabel.text = max(0, (floor(attachment.duration.defaulting(to: 0) - progress)))
 | |
|                     .formatted(format: .hoursMinutesSeconds)
 | |
|                 
 | |
|                 guard let duration: TimeInterval = attachment.duration, duration > 0, progress > 0 else {
 | |
|                     return progressViewRightConstraint.constant = -VoiceMessageView.width
 | |
|                 }
 | |
|                 
 | |
|                 let fraction: Double = (progress / duration)
 | |
|                 progressViewRightConstraint.constant = -(VoiceMessageView.width * (1 - fraction))
 | |
|                 
 | |
|                 // If the playback rate changed then show the 'speedUpLabel' briefly
 | |
|                 guard playbackRate > oldPlaybackRate else { return }
 | |
|                 
 | |
|                 UIView.animate(withDuration: 0.25) { [weak self] in
 | |
|                     self?.countdownLabel.alpha = 0
 | |
|                     self?.speedUpLabel.alpha = 1
 | |
|                 }
 | |
|                 
 | |
|                 DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1250)) {
 | |
|                     UIView.animate(withDuration: 0.25) { [weak self] in
 | |
|                         self?.countdownLabel.alpha = 1
 | |
|                         self?.speedUpLabel.alpha = 0
 | |
|                     }
 | |
|                 }
 | |
|                 
 | |
|             default:
 | |
|                 if !loader.isAnimating {
 | |
|                     loader.startAnimating()
 | |
|                 }
 | |
|         }
 | |
|     }
 | |
| }
 |