// // PollOptionView.swift // Mastodon // // Created by MainasuK Cirno on 2021-3-23. // import UIKit import Combine import MastodonAsset import MastodonLocalization public final class PollOptionView: UIView { public static let height: CGFloat = optionHeight + 2 * verticalMargin public static let optionHeight: CGFloat = 44 public static let verticalMargin: CGFloat = 5 public static let checkmarkImageSize = CGSize(width: 26, height: 26) public static let checkmarkBackgroundLeadingMargin: CGFloat = 9 private var viewStateDisposeBag = Set() public var disposeBag = Set() public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(view: self) return viewModel }() public private(set) var style: Style? public let roundedBackgroundView = UIView() public let voteProgressStripView: StripProgressView = { let view = StripProgressView() view.tintColor = Asset.Colors.Brand.blurple.color return view }() public let checkmarkBackgroundView: UIView = { let view = UIView() return view }() public let checkmarkImageView: UIImageView = { let imageView = UIImageView() let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))! imageView.image = image.withRenderingMode(.alwaysTemplate) imageView.tintColor = Asset.Colors.Brand.blurple.color return imageView }() public let plusCircleImageView: UIImageView = { let imageView = UIImageView() let image = Asset.Circles.plusCircle.image imageView.image = image.withRenderingMode(.alwaysTemplate) imageView.tintColor = Asset.Colors.Brand.blurple.color return imageView }() public let optionTextField: DeleteBackwardResponseTextField = { let textField = DeleteBackwardResponseTextField() textField.font = .systemFont(ofSize: 15, weight: .medium) textField.textColor = Asset.Colors.Label.primary.color textField.text = "Option" textField.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .left : .right return textField }() public let optionLabelMiddlePaddingView = UIView() public let optionPercentageLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 13, weight: .regular) label.textColor = Asset.Colors.Label.primary.color label.text = "50%" label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .right : .left return label }() public func prepareForReuse() { disposeBag.removeAll() viewModel.objects.removeAll() viewModel.percentage = nil voteProgressStripView.setProgress(0, animated: false) } public override init(frame: CGRect) { super.init(frame: frame) _init() } public required init?(coder: NSCoder) { super.init(coder: coder) _init() } } extension PollOptionView { private func _init() { roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false addSubview(roundedBackgroundView) NSLayoutConstraint.activate([ roundedBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: 5), roundedBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor), roundedBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor, constant: 5), roundedBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionView.optionHeight).priority(.defaultHigh), ]) voteProgressStripView.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(voteProgressStripView) voteProgressStripView.pinToParent() checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(checkmarkBackgroundView) NSLayoutConstraint.activate([ checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9), checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: PollOptionView.checkmarkBackgroundLeadingMargin), roundedBackgroundView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor, constant: 9), checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollOptionView.checkmarkImageSize.width).priority(.required - 1), checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionView.checkmarkImageSize.height).priority(.required - 1), ]) checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false checkmarkBackgroundView.addSubview(checkmarkImageView) NSLayoutConstraint.activate([ checkmarkImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor, constant: 5), checkmarkImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor, constant: 5), checkmarkBackgroundView.trailingAnchor.constraint(equalTo: checkmarkImageView.trailingAnchor, constant: 5), checkmarkBackgroundView.bottomAnchor.constraint(equalTo: checkmarkImageView.bottomAnchor, constant: 5), ]) plusCircleImageView.translatesAutoresizingMaskIntoConstraints = false addSubview(plusCircleImageView) plusCircleImageView.pinTo(to: checkmarkBackgroundView) optionTextField.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(optionTextField) NSLayoutConstraint.activate([ optionTextField.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor, constant: 14), optionTextField.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor), optionTextField.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), ]) optionLabelMiddlePaddingView.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(optionLabelMiddlePaddingView) NSLayoutConstraint.activate([ optionLabelMiddlePaddingView.leadingAnchor.constraint(equalTo: optionTextField.trailingAnchor), optionLabelMiddlePaddingView.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor), optionLabelMiddlePaddingView.heightAnchor.constraint(equalToConstant: 4).priority(.defaultHigh), optionLabelMiddlePaddingView.widthAnchor.constraint(greaterThanOrEqualToConstant: 8).priority(.defaultLow), ]) optionLabelMiddlePaddingView.setContentHuggingPriority(.required - 1, for: .horizontal) optionLabelMiddlePaddingView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) optionPercentageLabel.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(optionPercentageLabel) NSLayoutConstraint.activate([ optionPercentageLabel.leadingAnchor.constraint(equalTo: optionLabelMiddlePaddingView.trailingAnchor), roundedBackgroundView.trailingAnchor.constraint(equalTo: optionPercentageLabel.trailingAnchor, constant: 18), optionPercentageLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor), ]) optionPercentageLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) optionPercentageLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) plusCircleImageView.isHidden = true updateCornerRadius() isAccessibilityElement = true } public override var accessibilityLabel: String? { get { switch viewModel.voteState { case .reveal: return [ optionTextField, optionPercentageLabel ] .compactMap { $0.accessibilityLabel } .joined(separator: ", ") case .hidden: return optionTextField.accessibilityLabel } } set { } } public override func layoutSubviews() { super.layoutSubviews() updateCornerRadius() viewModel.layoutDidUpdate.send() } public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) viewModel.layoutDidUpdate.send() } } extension PollOptionView { public enum Style { case plain case edit } public func setup(style: Style) { guard self.style == nil else { assertionFailure("Should only setup once") return } self.style = style self.viewModel.style = style } } extension PollOptionView { private func updateCornerRadius() { roundedBackgroundView.layer.masksToBounds = true roundedBackgroundView.layer.cornerRadius = PollOptionView.optionHeight * 0.5 roundedBackgroundView.layer.cornerCurve = .circular checkmarkBackgroundView.layer.masksToBounds = true checkmarkBackgroundView.layer.cornerRadius = PollOptionView.checkmarkImageSize.width * 0.5 checkmarkBackgroundView.layer.cornerCurve = .circular } } #if canImport(SwiftUI) && DEBUG import SwiftUI struct PollOptionView_Previews: PreviewProvider { static var previews: some View { Group { UIViewPreview(width: 375) { PollOptionView() } .previewLayout(.fixed(width: 375, height: 100)) UIViewPreview(width: 375) { PollOptionView() } .preferredColorScheme(.dark) .previewLayout(.fixed(width: 375, height: 100)) } } } #endif