// // Button.swift // VDS // // Created by Jarrod Courtney on 9/16/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine public class Button:ButtonBase{} open class ButtonBase: UIButton, ModelHandlerable, ViewProtocol, Resettable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- @Published public var model: ModelType = ModelType() public var modelPublisher: Published.Publisher { $model } public var subscribers = Set() //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var mainStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.alignment = .top $0.axis = .vertical $0.spacing = 0 } }() private var buttonWidthConstraint: NSLayoutConstraint? private var buttonHeightConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- private let PaddingOneHalf: CGFloat = 2 private let PaddingOne: CGFloat = 4 private let PaddingTwo: CGFloat = 8 private let PaddingThree: CGFloat = 12 private let PaddingFour: CGFloat = 16 private let PaddingFive: CGFloat = 24 private let PaddingEight: CGFloat = 32 private let PaddingTen: CGFloat = 40 private let PaddingTwelve: CGFloat = 48 private let PaddingEighteen: CGFloat = 72 @Proxy(\.model.surface) open var surface: Surface @Proxy(\.model.use) open var use: Use open var buttonSize: Use.Size = .large { didSet { model.buttonSize = buttonSize } } @Proxy(\.model.disabled) open var disabled: Bool { didSet { self.isEnabled = !disabled } } open var buttonWidth: CGFloat = 200 { didSet { self.updateView(viewModel: model) } } open override var isEnabled: Bool { get { !model.disabled } set { //create local vars for clear coding let disabled = !newValue if model.disabled != disabled { model.disabled = disabled } isUserInteractionEnabled = isEnabled } } @Proxy(\.model.attributes) open var attributes: [any LabelAttributeModel]? //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- private var buttonBackgroundColorConfiguration: UseableColorConfiguration = { return UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.backgroundPrimaryDark $0.primary.enabled.darkColor = VDSColor.backgroundPrimaryLight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark $0.secondary.enabled.lightColor = UIColor.clear $0.secondary.enabled.darkColor = UIColor.clear $0.secondary.disabled.lightColor = UIColor.clear $0.secondary.disabled.darkColor = UIColor.clear } }() private var buttonBorderColorConfiguration: UseableColorConfiguration = { return UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark } }() private var buttonTitleColorConfiguration: UseableColorConfiguration = { return UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.disabled.darkColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark } }() //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) initialSetup() } public required init(with model: ModelType) { super.init(frame: .zero) initialSetup() set(with: model) } public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() set(with: model) } public required init?(coder: NSCoder) { super.init(coder: coder) initialSetup() } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- open func initialSetup() { backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false accessibilityCustomActions = [] accessibilityTraits = .staticText setupUpdateView() setup() } open func setup() { self.translatesAutoresizingMaskIntoConstraints = false mainStackView.addArrangedSubview(self) mainStackView.spacing = 12 } open func reset() { // text = nil accessibilityCustomActions = [] accessibilityTraits = .staticText } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open func updateView(viewModel: ModelType) { if let text = viewModel.text { self.setTitle(text, for: .normal) } else { self.setTitle("No ViewModel Text", for: .normal) } let backgroundColor = buttonBackgroundColorConfiguration.getColor(viewModel) let borderColor = buttonBorderColorConfiguration.getColor(viewModel) let borderWidth = viewModel.use == .secondary ? 1.0 : 0.0 let titleColor = buttonTitleColorConfiguration.getColor(viewModel) let buttonHeight = viewModel.buttonSize.getHeight() let minWidth = buttonWidth >= viewModel.buttonSize.minimumWidth() ? buttonWidth : viewModel.buttonSize.minimumWidth() self.titleLabel?.font = viewModel.buttonSize == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font self.backgroundColor = backgroundColor self.setTitleColor(titleColor, for: .normal) self.layer.borderColor = borderColor.cgColor self.layer.cornerRadius = buttonHeight / 2 self.layer.borderWidth = borderWidth if buttonWidthConstraint == nil { buttonWidthConstraint = self.widthAnchor.constraint(equalToConstant: minWidth) } else { buttonWidthConstraint?.constant = minWidth } buttonWidthConstraint?.isActive = true if buttonHeightConstraint == nil { buttonHeightConstraint = self.heightAnchor.constraint(equalToConstant: buttonHeight) } else { buttonHeightConstraint?.constant = buttonHeight } buttonHeightConstraint?.isActive = true contentEdgeInsets = getContentEdgeInsets(viewModel: viewModel) } //-------------------------------------------------- // MARK: - PRIVATE //-------------------------------------------------- private class UseableColorConfiguration : Colorable { public var primary = DisabledSurfaceColorConfiguration() public var secondary = DisabledSurfaceColorConfiguration() required public init(){} public func getColor(_ viewModel: ModelType) -> UIColor { return viewModel.use == .primary ? primary.getColor(viewModel) : secondary.getColor(viewModel) } } private func getContentEdgeInsets(viewModel: ModelType) -> UIEdgeInsets { var verticalPadding = 0.0 var horizontalPadding = 0.0 switch viewModel.buttonSize { case .large: verticalPadding = PaddingThree horizontalPadding = PaddingFive break case .small: verticalPadding = PaddingTwo horizontalPadding = PaddingFour break } return UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) } }