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.
		
		
		
		
		
			
		
			
				
	
	
		
			225 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			225 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import UIKit
 | 
						|
import NVActivityIndicatorView
 | 
						|
import SessionUIKit
 | 
						|
import SessionMessagingKit
 | 
						|
 | 
						|
final class LinkPreviewView: UIView {
 | 
						|
    private static let loaderSize: CGFloat = 24
 | 
						|
    private static let cancelButtonSize: CGFloat = 45
 | 
						|
    
 | 
						|
    private let maxWidth: CGFloat
 | 
						|
    private let onCancel: (() -> ())?
 | 
						|
 | 
						|
    // MARK: - UI
 | 
						|
    
 | 
						|
    private lazy var imageViewContainerWidthConstraint = imageView.set(.width, to: 100)
 | 
						|
    private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100)
 | 
						|
 | 
						|
    // MARK: UI Components
 | 
						|
 | 
						|
    private lazy var imageView: UIImageView = {
 | 
						|
        let result: UIImageView = UIImageView()
 | 
						|
        result.contentMode = .scaleAspectFill
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
 | 
						|
    private lazy var imageViewContainer: UIView = {
 | 
						|
        let result: UIView = UIView()
 | 
						|
        result.clipsToBounds = true
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
 | 
						|
    private let loader: NVActivityIndicatorView = {
 | 
						|
        let result: NVActivityIndicatorView = NVActivityIndicatorView(
 | 
						|
            frame: CGRect.zero,
 | 
						|
            type: .circleStrokeSpin,
 | 
						|
            color: .black,
 | 
						|
            padding: nil
 | 
						|
        )
 | 
						|
        
 | 
						|
        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 titleLabel: UILabel = {
 | 
						|
        let result: UILabel = UILabel()
 | 
						|
        result.font = .boldSystemFont(ofSize: Values.smallFontSize)
 | 
						|
        result.numberOfLines = 0
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
 | 
						|
    private lazy var bodyTappableLabelContainer: UIView = UIView()
 | 
						|
 | 
						|
    private lazy var hStackViewContainer: UIView = UIView()
 | 
						|
 | 
						|
    private lazy var hStackView: UIStackView = UIStackView()
 | 
						|
 | 
						|
    private lazy var cancelButton: UIButton = {
 | 
						|
        let result: UIButton = UIButton(type: .custom)
 | 
						|
        result.setImage(
 | 
						|
            UIImage(named: "X")?
 | 
						|
                .withRenderingMode(.alwaysTemplate),
 | 
						|
            for: .normal
 | 
						|
        )
 | 
						|
        result.themeTintColor = .textPrimary
 | 
						|
        
 | 
						|
        let cancelButtonSize = LinkPreviewView.cancelButtonSize
 | 
						|
        result.set(.width, to: cancelButtonSize)
 | 
						|
        result.set(.height, to: cancelButtonSize)
 | 
						|
        result.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside)
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    var bodyTappableLabel: TappableLabel?
 | 
						|
 | 
						|
    // MARK: - Initialization
 | 
						|
    
 | 
						|
    init(maxWidth: CGFloat, onCancel: (() -> ())? = nil) {
 | 
						|
        self.maxWidth = maxWidth
 | 
						|
        self.onCancel = onCancel
 | 
						|
        
 | 
						|
        super.init(frame: CGRect.zero)
 | 
						|
        
 | 
						|
        setUpViewHierarchy()
 | 
						|
    }
 | 
						|
 | 
						|
    override init(frame: CGRect) {
 | 
						|
        preconditionFailure("Use init(for:maxWidth:delegate:) instead.")
 | 
						|
    }
 | 
						|
 | 
						|
    required init?(coder: NSCoder) {
 | 
						|
        preconditionFailure("Use init(for:maxWidth:delegate:) instead.")
 | 
						|
    }
 | 
						|
 | 
						|
    private func setUpViewHierarchy() {
 | 
						|
        // Image view
 | 
						|
        imageViewContainerWidthConstraint.isActive = true
 | 
						|
        imageViewContainerHeightConstraint.isActive = true
 | 
						|
        imageViewContainer.addSubview(imageView)
 | 
						|
        imageView.pin(to: imageViewContainer)
 | 
						|
        
 | 
						|
        // Title label
 | 
						|
        let titleLabelContainer = UIView()
 | 
						|
        titleLabelContainer.addSubview(titleLabel)
 | 
						|
        titleLabel.pin(to: titleLabelContainer, withInset: Values.mediumSpacing)
 | 
						|
        
 | 
						|
        // Horizontal stack view
 | 
						|
        hStackView.addArrangedSubview(imageViewContainer)
 | 
						|
        hStackView.addArrangedSubview(titleLabelContainer)
 | 
						|
        hStackView.axis = .horizontal
 | 
						|
        hStackView.alignment = .center
 | 
						|
        hStackViewContainer.addSubview(hStackView)
 | 
						|
        hStackView.pin(to: hStackViewContainer)
 | 
						|
        
 | 
						|
        // Vertical stack view
 | 
						|
        let vStackView = UIStackView(arrangedSubviews: [ hStackViewContainer, bodyTappableLabelContainer ])
 | 
						|
        vStackView.axis = .vertical
 | 
						|
        addSubview(vStackView)
 | 
						|
        vStackView.pin(to: self)
 | 
						|
        
 | 
						|
        // Loader
 | 
						|
        addSubview(loader)
 | 
						|
        
 | 
						|
        let loaderSize = LinkPreviewView.loaderSize
 | 
						|
        loader.set(.width, to: loaderSize)
 | 
						|
        loader.set(.height, to: loaderSize)
 | 
						|
        loader.center(in: self)
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: - Updating
 | 
						|
    
 | 
						|
    public func update(
 | 
						|
        with state: LinkPreviewState,
 | 
						|
        isOutgoing: Bool,
 | 
						|
        delegate: TappableLabelDelegate? = nil,
 | 
						|
        cellViewModel: MessageViewModel? = nil,
 | 
						|
        bodyLabelTextColor: ThemeValue? = nil,
 | 
						|
        lastSearchText: String? = nil
 | 
						|
    ) {
 | 
						|
        cancelButton.removeFromSuperview()
 | 
						|
        
 | 
						|
        var image: UIImage? = state.image
 | 
						|
        let stateHasImage: Bool = (image != nil)
 | 
						|
        if image == nil && (state is LinkPreview.DraftState || state is LinkPreview.SentState) {
 | 
						|
            image = UIImage(named: "Link")?.withRenderingMode(.alwaysTemplate)
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Image view
 | 
						|
        let imageViewContainerSize: CGFloat = (state is LinkPreview.SentState ? 100 : 80)
 | 
						|
        imageViewContainerWidthConstraint.constant = imageViewContainerSize
 | 
						|
        imageViewContainerHeightConstraint.constant = imageViewContainerSize
 | 
						|
        imageViewContainer.layer.cornerRadius = (state is LinkPreview.SentState ? 0 : 8)
 | 
						|
        
 | 
						|
        imageView.image = image
 | 
						|
        imageView.themeTintColor = (isOutgoing ?
 | 
						|
            .messageBubble_outgoingText :
 | 
						|
            .messageBubble_incomingText
 | 
						|
        )
 | 
						|
        imageView.contentMode = (stateHasImage ? .scaleAspectFill : .center)
 | 
						|
        
 | 
						|
        // Loader
 | 
						|
        loader.alpha = (image != nil ? 0 : 1)
 | 
						|
        if image != nil { loader.stopAnimating() } else { loader.startAnimating() }
 | 
						|
        
 | 
						|
        // Title
 | 
						|
        titleLabel.text = state.title
 | 
						|
        titleLabel.themeTextColor = (isOutgoing ?
 | 
						|
            .messageBubble_outgoingText :
 | 
						|
            .messageBubble_incomingText
 | 
						|
        )
 | 
						|
        
 | 
						|
        // Horizontal stack view
 | 
						|
        switch state {
 | 
						|
            case is LinkPreview.LoadingState:
 | 
						|
                imageViewContainer.themeBackgroundColor = .clear
 | 
						|
                hStackViewContainer.themeBackgroundColor = nil
 | 
						|
                
 | 
						|
            case is LinkPreview.SentState:
 | 
						|
                imageViewContainer.themeBackgroundColor = .messageBubble_overlay
 | 
						|
                hStackViewContainer.themeBackgroundColor = .messageBubble_overlay
 | 
						|
                
 | 
						|
            default:
 | 
						|
                imageViewContainer.themeBackgroundColor = .messageBubble_overlay
 | 
						|
                hStackViewContainer.themeBackgroundColor = nil
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Body text view
 | 
						|
        bodyTappableLabelContainer.subviews.forEach { $0.removeFromSuperview() }
 | 
						|
        
 | 
						|
        if let cellViewModel: MessageViewModel = cellViewModel {
 | 
						|
            let bodyTappableLabel = VisibleMessageCell.getBodyTappableLabel(
 | 
						|
                for: cellViewModel,
 | 
						|
                with: maxWidth,
 | 
						|
                textColor: (bodyLabelTextColor ?? .textPrimary),
 | 
						|
                searchText: lastSearchText,
 | 
						|
                delegate: delegate
 | 
						|
            )
 | 
						|
            
 | 
						|
            self.bodyTappableLabel = bodyTappableLabel
 | 
						|
            bodyTappableLabelContainer.addSubview(bodyTappableLabel)
 | 
						|
            bodyTappableLabel.pin(to: bodyTappableLabelContainer, withInset: 12)
 | 
						|
        }
 | 
						|
        
 | 
						|
        if state is LinkPreview.DraftState {
 | 
						|
            hStackView.addArrangedSubview(cancelButton)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: - Interaction
 | 
						|
    
 | 
						|
    @objc private func cancel() {
 | 
						|
        onCancel?()
 | 
						|
    }
 | 
						|
}
 |