mirror of https://github.com/oxen-io/session-ios
				
				
				
			Add rough draft of link preview view to composer.
							parent
							
								
									977ee9ffe9
								
							
						
					
					
						commit
						416aa2b347
					
				@ -0,0 +1,417 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public enum LinkPreviewImageState: Int {
 | 
				
			||||||
 | 
					    case none
 | 
				
			||||||
 | 
					    case loading
 | 
				
			||||||
 | 
					    case loaded
 | 
				
			||||||
 | 
					    case invalid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public protocol LinkPreviewState {
 | 
				
			||||||
 | 
					    func isLoaded() -> Bool
 | 
				
			||||||
 | 
					    func urlString() -> String?
 | 
				
			||||||
 | 
					    func displayDomain() -> String?
 | 
				
			||||||
 | 
					    func title() -> String?
 | 
				
			||||||
 | 
					    func imageState() -> LinkPreviewImageState
 | 
				
			||||||
 | 
					    func image() -> UIImage?
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public class LinkPreviewLoading: NSObject, LinkPreviewState {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override init() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func isLoaded() -> Bool {
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func urlString() -> String? {
 | 
				
			||||||
 | 
					        return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func displayDomain() -> String? {
 | 
				
			||||||
 | 
					        return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func title() -> String? {
 | 
				
			||||||
 | 
					        return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func imageState() -> LinkPreviewImageState {
 | 
				
			||||||
 | 
					        return .none
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func image() -> UIImage? {
 | 
				
			||||||
 | 
					        return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public class LinkPreviewDraft: NSObject, LinkPreviewState {
 | 
				
			||||||
 | 
					    private let linkPreviewDraft: OWSLinkPreviewDraft
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @objc
 | 
				
			||||||
 | 
					    public required init(linkPreviewDraft: OWSLinkPreviewDraft) {
 | 
				
			||||||
 | 
					        self.linkPreviewDraft = linkPreviewDraft
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func isLoaded() -> Bool {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func urlString() -> String? {
 | 
				
			||||||
 | 
					        return linkPreviewDraft.urlString
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func displayDomain() -> String? {
 | 
				
			||||||
 | 
					        guard let displayDomain = linkPreviewDraft.displayDomain() else {
 | 
				
			||||||
 | 
					            owsFailDebug("Missing display domain")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return displayDomain
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func title() -> String? {
 | 
				
			||||||
 | 
					        return linkPreviewDraft.title
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func imageState() -> LinkPreviewImageState {
 | 
				
			||||||
 | 
					        if linkPreviewDraft.imageFilePath != nil {
 | 
				
			||||||
 | 
					            return .loaded
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return .none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func image() -> UIImage? {
 | 
				
			||||||
 | 
					        assert(imageState() == .loaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guard let imageFilepath = linkPreviewDraft.imageFilePath else {
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let image = UIImage(contentsOfFile: imageFilepath) else {
 | 
				
			||||||
 | 
					            owsFail("Could not load image: \(imageFilepath)")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return image
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public class LinkPreviewSent: NSObject, LinkPreviewState {
 | 
				
			||||||
 | 
					    private let linkPreview: OWSLinkPreview
 | 
				
			||||||
 | 
					    private let imageAttachment: TSAttachment?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @objc
 | 
				
			||||||
 | 
					    public required init(linkPreview: OWSLinkPreview,
 | 
				
			||||||
 | 
					                  imageAttachment: TSAttachment?) {
 | 
				
			||||||
 | 
					        self.linkPreview = linkPreview
 | 
				
			||||||
 | 
					        self.imageAttachment = imageAttachment
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func isLoaded() -> Bool {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func urlString() -> String? {
 | 
				
			||||||
 | 
					        guard let urlString = linkPreview.urlString else {
 | 
				
			||||||
 | 
					            owsFailDebug("Missing url")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return urlString
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func displayDomain() -> String? {
 | 
				
			||||||
 | 
					        guard let displayDomain = linkPreview.displayDomain() else {
 | 
				
			||||||
 | 
					            owsFailDebug("Missing display domain")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return displayDomain
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func title() -> String? {
 | 
				
			||||||
 | 
					        return linkPreview.title
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func imageState() -> LinkPreviewImageState {
 | 
				
			||||||
 | 
					        guard linkPreview.imageAttachmentId != nil else {
 | 
				
			||||||
 | 
					            return .none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let imageAttachment = imageAttachment else {
 | 
				
			||||||
 | 
					            owsFailDebug("Missing imageAttachment.")
 | 
				
			||||||
 | 
					            return .none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let attachmentStream = imageAttachment as? TSAttachmentStream else {
 | 
				
			||||||
 | 
					            return .loading
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard attachmentStream.isValidImage else {
 | 
				
			||||||
 | 
					            return .invalid
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return .loaded
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public func image() -> UIImage? {
 | 
				
			||||||
 | 
					        assert(imageState() == .loaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guard let attachmentStream = imageAttachment as? TSAttachmentStream else {
 | 
				
			||||||
 | 
					            owsFailDebug("Could not load image.")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard attachmentStream.isValidImage else {
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let imageFilepath = attachmentStream.originalFilePath else {
 | 
				
			||||||
 | 
					            owsFailDebug("Attachment is missing file path.")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let image = UIImage(contentsOfFile: imageFilepath) else {
 | 
				
			||||||
 | 
					            owsFail("Could not load image: \(imageFilepath)")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return image
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public protocol LinkPreviewViewDelegate {
 | 
				
			||||||
 | 
					    func linkPreviewCanCancel() -> Bool
 | 
				
			||||||
 | 
					    @objc optional func linkPreviewDidCancel()
 | 
				
			||||||
 | 
					    @objc optional func linkPreviewDidTap(urlString: String?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@objc
 | 
				
			||||||
 | 
					public class LinkPreviewView: UIStackView {
 | 
				
			||||||
 | 
					    private weak var delegate: LinkPreviewViewDelegate?
 | 
				
			||||||
 | 
					    private let state: LinkPreviewState
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @available(*, unavailable, message:"use other constructor instead.")
 | 
				
			||||||
 | 
					    required init(coder aDecoder: NSCoder) {
 | 
				
			||||||
 | 
					        notImplemented()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @available(*, unavailable, message:"use other constructor instead.")
 | 
				
			||||||
 | 
					    override init(frame: CGRect) {
 | 
				
			||||||
 | 
					        notImplemented()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private let imageView = UIImageView()
 | 
				
			||||||
 | 
					    private let titleLabel = UILabel()
 | 
				
			||||||
 | 
					    private let domainLabel = UILabel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @objc
 | 
				
			||||||
 | 
					    public init(state: LinkPreviewState,
 | 
				
			||||||
 | 
					                delegate: LinkPreviewViewDelegate?) {
 | 
				
			||||||
 | 
					        self.state = state
 | 
				
			||||||
 | 
					        self.delegate = delegate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.init(frame: .zero)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        createContents()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var isApproval: Bool {
 | 
				
			||||||
 | 
					        return delegate != nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func createContents() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.isUserInteractionEnabled = true
 | 
				
			||||||
 | 
					        self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guard state.isLoaded() else {
 | 
				
			||||||
 | 
					            createLoadingContents()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard isApproval else {
 | 
				
			||||||
 | 
					            createMessageContents()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        createApprovalContents()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func createMessageContents() {
 | 
				
			||||||
 | 
					//        guard state.isLoaded() else {
 | 
				
			||||||
 | 
					//            createLoadingContents()
 | 
				
			||||||
 | 
					//            return
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        if let imageView = createImageView() {
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        switch state.imageState() {
 | 
				
			||||||
 | 
					//        case .loaded:
 | 
				
			||||||
 | 
					//            guard
 | 
				
			||||||
 | 
					//                let imageView = UIImageView()
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        case .loading:
 | 
				
			||||||
 | 
					//        default:
 | 
				
			||||||
 | 
					//            break
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        let textStack = UIStackView()
 | 
				
			||||||
 | 
					//        self.axis = .vertical
 | 
				
			||||||
 | 
					//        self.alignment = .leading
 | 
				
			||||||
 | 
					//        self.spacing = 5
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private let approvalHeight: CGFloat = 76
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var cancelButton: UIImageView?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func createApprovalContents() {
 | 
				
			||||||
 | 
					        self.axis = .horizontal
 | 
				
			||||||
 | 
					        self.alignment = .fill
 | 
				
			||||||
 | 
					        self.distribution = .equalSpacing
 | 
				
			||||||
 | 
					        self.spacing = 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let imageView = createImageView() {
 | 
				
			||||||
 | 
					            imageView.contentMode = .scaleAspectFill
 | 
				
			||||||
 | 
					            imageView.autoPinToSquareAspectRatio()
 | 
				
			||||||
 | 
					            let imageSize = approvalHeight
 | 
				
			||||||
 | 
					            imageView.autoSetDimensions(to: CGSize(width: imageSize, height: imageSize))
 | 
				
			||||||
 | 
					            imageView.setContentHuggingHigh()
 | 
				
			||||||
 | 
					            imageView.setCompressionResistanceHigh()
 | 
				
			||||||
 | 
					            imageView.clipsToBounds = true
 | 
				
			||||||
 | 
					            // TODO: Cropping, stroke.
 | 
				
			||||||
 | 
					            addArrangedSubview(imageView)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Right
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let rightStack = UIStackView()
 | 
				
			||||||
 | 
					        rightStack.axis = .horizontal
 | 
				
			||||||
 | 
					        rightStack.alignment = .fill
 | 
				
			||||||
 | 
					        rightStack.distribution = .equalSpacing
 | 
				
			||||||
 | 
					        rightStack.spacing = 8
 | 
				
			||||||
 | 
					        rightStack.setContentHuggingHorizontalLow()
 | 
				
			||||||
 | 
					        rightStack.setCompressionResistanceHorizontalLow()
 | 
				
			||||||
 | 
					        addArrangedSubview(rightStack)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let textStack = UIStackView()
 | 
				
			||||||
 | 
					        textStack.axis = .vertical
 | 
				
			||||||
 | 
					        textStack.alignment = .leading
 | 
				
			||||||
 | 
					        textStack.spacing = 2
 | 
				
			||||||
 | 
					        textStack.setContentHuggingHorizontalLow()
 | 
				
			||||||
 | 
					        textStack.setCompressionResistanceHorizontalLow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let title = state.title(),
 | 
				
			||||||
 | 
					            title.count > 0 {
 | 
				
			||||||
 | 
					            let label = UILabel()
 | 
				
			||||||
 | 
					            label.text = title
 | 
				
			||||||
 | 
					            label.textColor = Theme.primaryColor
 | 
				
			||||||
 | 
					            label.font = UIFont.ows_dynamicTypeBody
 | 
				
			||||||
 | 
					            textStack.addArrangedSubview(label)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let displayDomain = state.displayDomain(),
 | 
				
			||||||
 | 
					            displayDomain.count > 0 {
 | 
				
			||||||
 | 
					            let label = UILabel()
 | 
				
			||||||
 | 
					            label.text = displayDomain.uppercased()
 | 
				
			||||||
 | 
					            label.textColor = Theme.secondaryColor
 | 
				
			||||||
 | 
					            label.font = UIFont.ows_dynamicTypeCaption1
 | 
				
			||||||
 | 
					            textStack.addArrangedSubview(label)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let textWrapper = UIStackView(arrangedSubviews: [textStack])
 | 
				
			||||||
 | 
					        textWrapper.axis = .horizontal
 | 
				
			||||||
 | 
					        textWrapper.alignment = .center
 | 
				
			||||||
 | 
					        textWrapper.setContentHuggingHorizontalLow()
 | 
				
			||||||
 | 
					        textWrapper.setCompressionResistanceHorizontalLow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rightStack.addArrangedSubview(textWrapper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Cancel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cancelStack = UIStackView()
 | 
				
			||||||
 | 
					        cancelStack.axis = .horizontal
 | 
				
			||||||
 | 
					        cancelStack.alignment = .top
 | 
				
			||||||
 | 
					        cancelStack.setContentHuggingHigh()
 | 
				
			||||||
 | 
					        cancelStack.setCompressionResistanceHigh()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cancelImage: UIImage = #imageLiteral(resourceName: "quoted-message-cancel").withRenderingMode(.alwaysTemplate)
 | 
				
			||||||
 | 
					        let cancelButton = UIImageView(image: cancelImage)
 | 
				
			||||||
 | 
					        self.cancelButton = cancelButton
 | 
				
			||||||
 | 
					        cancelButton.tintColor = Theme.secondaryColor
 | 
				
			||||||
 | 
					        cancelButton.setContentHuggingHigh()
 | 
				
			||||||
 | 
					        cancelButton.setCompressionResistanceHigh()
 | 
				
			||||||
 | 
					        cancelStack.addArrangedSubview(cancelButton)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rightStack.addArrangedSubview(cancelStack)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Stroke
 | 
				
			||||||
 | 
					        let strokeView = UIView()
 | 
				
			||||||
 | 
					        strokeView.backgroundColor = Theme.secondaryColor
 | 
				
			||||||
 | 
					        rightStack.addSubview(strokeView)
 | 
				
			||||||
 | 
					        strokeView.autoPinWidthToSuperview()
 | 
				
			||||||
 | 
					        strokeView.autoPinEdge(toSuperviewEdge: .bottom)
 | 
				
			||||||
 | 
					        strokeView.autoSetDimension(.height, toSize: CGHairlineWidth())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func createImageView() -> UIImageView? {
 | 
				
			||||||
 | 
					        guard state.isLoaded() else {
 | 
				
			||||||
 | 
					            owsFailDebug("State not loaded.")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guard state.imageState()  == .loaded else {
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guard let image = state.image() else {
 | 
				
			||||||
 | 
					            owsFailDebug("Could not load image.")
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let imageView = UIImageView()
 | 
				
			||||||
 | 
					        imageView.image = image
 | 
				
			||||||
 | 
					        return imageView
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func createLoadingContents() {
 | 
				
			||||||
 | 
					        self.axis = .vertical
 | 
				
			||||||
 | 
					        self.alignment = .center
 | 
				
			||||||
 | 
					        self.autoSetDimension(.height, toSize: approvalHeight)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let label = UILabel()
 | 
				
			||||||
 | 
					        label.text = NSLocalizedString("LINK_PREVIEW_LOADING", comment: "Indicates that the link preview is being loaded.")
 | 
				
			||||||
 | 
					        label.textColor = Theme.secondaryColor
 | 
				
			||||||
 | 
					        label.font = UIFont.ows_dynamicTypeBody
 | 
				
			||||||
 | 
					        addArrangedSubview(label)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // MARK: Events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @objc func wasTapped(sender: UIGestureRecognizer) {
 | 
				
			||||||
 | 
					        guard sender.state == .recognized else {
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let cancelButton = cancelButton {
 | 
				
			||||||
 | 
					            let cancelLocation = sender.location(in: cancelButton)
 | 
				
			||||||
 | 
					            // Permissive hot area to make it very easy to cancel the link preview.
 | 
				
			||||||
 | 
					            let hotAreaInset: CGFloat = -20
 | 
				
			||||||
 | 
					            let cancelButtonHotArea = cancelButton.bounds.insetBy(dx: hotAreaInset, dy: hotAreaInset)
 | 
				
			||||||
 | 
					            if cancelButtonHotArea.contains(cancelLocation) {
 | 
				
			||||||
 | 
					                self.delegate?.linkPreviewDidCancel?()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.delegate?.linkPreviewDidTap?(urlString: self.state.urlString())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
					Loading…
					
					
				
		Reference in New Issue