// // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalCoreKit import YYImage public class CVUtils { @available(*, unavailable, message: "use other init() instead.") private init() {} private static let workQueue_userInitiated: DispatchQueue = { DispatchQueue(label: "org.signal.conversation-view.user-initiated", qos: .userInitiated, autoreleaseFrequency: .workItem) }() private static let workQueue_userInteractive: DispatchQueue = { DispatchQueue(label: "org.signal.conversation-view.user-interactive", qos: .userInteractive, autoreleaseFrequency: .workItem) }() public static func workQueue(isInitialLoad: Bool) -> DispatchQueue { isInitialLoad ? workQueue_userInteractive : workQueue_userInitiated } } // MARK: - public protocol CVView: UIView { func reset() } // MARK: - open class CVLabel: UILabel, CVView { public override func updateConstraints() { super.updateConstraints() deactivateAllConstraints() } public func reset() { // NOTE: we have to reset the attributed text and then the text; // this is the magic incantation that prevents properties from // a previously-set attributed string from applying to subsequent // attributed strings. self.attributedText = nil self.text = nil } } open class CVButton: OWSButton, CVView { open override func updateConstraints() { super.updateConstraints() deactivateAllConstraints() } public func reset() { self.block = {} self.dimsWhenDisabled = false self.dimsWhenHighlighted = false self.contentEdgeInsets = .zero self.titleEdgeInsets = .zero [ UIControl.State.normal, .highlighted, .disabled, .selected, .focused, .application, .reserved, ].forEach { controlState in self.setAttributedTitle(nil, for: controlState) self.setTitle(nil, for: controlState) self.setImage(nil, for: controlState) } } } // MARK: - open class CVImageView: UIImageView, CVView { public override func updateConstraints() { super.updateConstraints() deactivateAllConstraints() } public func reset() { self.image = nil } // MARK: - Layout public typealias LayoutBlock = (UIView) -> Void private var layoutBlocks = [LayoutBlock]() public func addLayoutBlock(_ layoutBlock: @escaping LayoutBlock) { layoutBlocks.append(layoutBlock) } public override var bounds: CGRect { didSet { if oldValue.size != bounds.size { viewSizeDidChange() } } } public override var frame: CGRect { didSet { if oldValue.size != frame.size { viewSizeDidChange() } } } func viewSizeDidChange() { layoutSubviews() } open override func layoutSubviews() { layoutSubviews(skipLayoutBlocks: false) } public func layoutSubviews(skipLayoutBlocks: Bool = false) { AssertIsOnMainThread() super.layoutSubviews() if !skipLayoutBlocks { applyLayoutBlocks() } } public func applyLayoutBlocks() { AssertIsOnMainThread() for layoutBlock in layoutBlocks { layoutBlock(self) } } // MARK: - Circles public static func circleView() -> CVImageView { let result = CVImageView() result.addLayoutBlock { view in view.layer.cornerRadius = min(view.width, view.height) * 0.5 } return result } } // MARK: - open class CVAnimatedImageView: YYAnimatedImageView, CVView { public override func updateConstraints() { super.updateConstraints() deactivateAllConstraints() } public func reset() { self.image = nil } }