import UIKit final class OptionView : UIView { private let title: String private let explanation: String private let delegate: OptionViewDelegate private let isRecommended: Bool var isSelected = false { didSet { handleIsSelectedChanged() } } init(title: String, explanation: String, delegate: OptionViewDelegate, isRecommended: Bool = false) { self.title = title self.explanation = explanation self.delegate = delegate self.isRecommended = isRecommended super.init(frame: CGRect.zero) setUpViewHierarchy() } override init(frame: CGRect) { preconditionFailure("Use init(string:explanation:) instead.") } required init?(coder: NSCoder) { preconditionFailure("Use init(string:explanation:) instead.") } private func setUpViewHierarchy() { backgroundColor = Colors.pnOptionBackground // Round corners layer.cornerRadius = Values.pnOptionCornerRadius // Set up border layer.borderWidth = Values.borderThickness layer.borderColor = Colors.pnOptionBorder.cgColor // Set up shadow layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0, height: 0.8) layer.shadowOpacity = isLightMode ? 0.4 : 1 layer.shadowRadius = isLightMode ? 4 : 6 // Set up title label let titleLabel = UILabel() titleLabel.textColor = Colors.text titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize) titleLabel.text = title titleLabel.numberOfLines = 0 titleLabel.lineBreakMode = .byWordWrapping // Set up explanation label let explanationLabel = UILabel() explanationLabel.textColor = Colors.text explanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize) explanationLabel.text = explanation explanationLabel.numberOfLines = 0 explanationLabel.lineBreakMode = .byWordWrapping // Set up stack view let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ]) stackView.axis = .vertical stackView.spacing = 4 stackView.alignment = .fill addSubview(stackView) stackView.pin(.leading, to: .leading, of: self, withInset: 12) stackView.pin(.top, to: .top, of: self, withInset: 12) self.pin(.trailing, to: .trailing, of: stackView, withInset: 12) self.pin(.bottom, to: .bottom, of: stackView, withInset: 12) // Set up recommended label if needed if isRecommended { let recommendedLabel = UILabel() recommendedLabel.textColor = Colors.accent recommendedLabel.font = .boldSystemFont(ofSize: Values.smallFontSize) recommendedLabel.text = NSLocalizedString("vc_pn_mode_recommended_option_tag", comment: "") stackView.addArrangedSubview(recommendedLabel) } // Set up tap gesture recognizer let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tapGestureRecognizer) } @objc private func handleTap() { isSelected = !isSelected } private func handleIsSelectedChanged() { let animationDuration: TimeInterval = 0.25 // Animate border color let newBorderColor = isSelected ? Colors.accent.cgColor : Colors.pnOptionBorder.cgColor let borderAnimation = CABasicAnimation(keyPath: "borderColor") borderAnimation.fromValue = layer.shadowColor borderAnimation.toValue = newBorderColor borderAnimation.duration = animationDuration layer.add(borderAnimation, forKey: borderAnimation.keyPath) layer.borderColor = newBorderColor // Animate shadow color let newShadowColor = isSelected ? Colors.newConversationButtonShadow.cgColor : UIColor.black.cgColor let shadowAnimation = CABasicAnimation(keyPath: "shadowColor") shadowAnimation.fromValue = layer.shadowColor shadowAnimation.toValue = newShadowColor shadowAnimation.duration = animationDuration layer.add(shadowAnimation, forKey: shadowAnimation.keyPath) layer.shadowColor = newShadowColor // Notify delegate if isSelected { delegate.optionViewDidActivate(self) } } } // MARK: Option View Delegate protocol OptionViewDelegate { func optionViewDidActivate(_ optionView: OptionView) }