diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 94de6eb8..30830c5a 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -313,6 +313,9 @@ D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */; }; D2E2A99C23D8D975000B42E6 /* ImageHeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99B23D8D975000B42E6 /* ImageHeadlineBodyModel.swift */; }; D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */; }; + D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99E23E07F8A000B42E6 /* PillButton.swift */; }; + D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */; }; + D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */; }; D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; @@ -633,6 +636,9 @@ D2E2A99723D8D63C000B42E6 /* ActionDetailWithImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImageModel.swift; sourceTree = ""; }; D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButtonModel.swift; sourceTree = ""; }; D2E2A99B23D8D975000B42E6 /* ImageHeadlineBodyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBodyModel.swift; sourceTree = ""; }; + D2E2A99E23E07F8A000B42E6 /* PillButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButton.swift; sourceTree = ""; }; + D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModelProtocol.swift; sourceTree = ""; }; + D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableableModelProtocol.swift; sourceTree = ""; }; D2F4DDE52371A4CB00CD28BB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackItem.swift; sourceTree = ""; }; @@ -668,6 +674,7 @@ 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, + D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -816,6 +823,7 @@ D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, + D282AACA2243C61700C46919 /* ButtonView.swift */, ); path = Views; sourceTree = ""; @@ -1144,11 +1152,11 @@ 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */, DBC4391A224421A0001AB423 /* CaretButton.swift */, D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, - D282AACA2243C61700C46919 /* ButtonView.swift */, + D2E2A99E23E07F8A000B42E6 /* PillButton.swift */, D28A838823CCCFCB00DFE4FC /* LinkModel.swift */, + C07065C32395677300FBF997 /* Link.swift */, D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */, D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */, - C07065C32395677300FBF997 /* Link.swift */, ); path = Buttons; sourceTree = ""; @@ -1346,6 +1354,7 @@ D2B18B7D236090D500A9AEDC /* BaseClasses */ = { isa = PBXGroup; children = ( + D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */, C003506023AA94CD00B6AC29 /* Button.swift */, D2B18B7E2360913400A9AEDC /* Control.swift */, D2B18B802360945C00A9AEDC /* View.swift */, @@ -1559,6 +1568,7 @@ D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */, 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, + D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, @@ -1574,6 +1584,7 @@ 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */, 012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */, + D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, @@ -1705,6 +1716,7 @@ D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, + D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift index 61e3ca3e..9eb284d7 100644 --- a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift @@ -18,14 +18,21 @@ public enum ButtonSize: String, Codable { case tiny } -public class ButtonModel: MoleculeModelProtocol { +public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol { public static var identifier: String = "button" public var moleculeName: String? public var backgroundColor: Color? public var title: String public var action: ActionModelProtocol + public var enabled: Bool = true public var style: ButtonStyle? public var size: ButtonSize? = .standard + public var fillColor: Color? + public var textColor: Color? + public var borderColor: Color? + public var disabledFillColor: Color? + public var disabledTextColor: Color? + public var disabledBorderColor: Color? public var required: Bool? public var requiredGroups: [String]? @@ -39,8 +46,15 @@ public class ButtonModel: MoleculeModelProtocol { case backgroundColor case title case action + case enabled case style case size + case fillColor + case textColor + case borderColor + case disabledFillColor + case disabledTextColor + case disabledBorderColor case required case requiredGroups } @@ -60,6 +74,15 @@ public class ButtonModel: MoleculeModelProtocol { if let size = try typeContainer.decodeIfPresent(ButtonSize.self, forKey: .size) { self.size = size } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) + textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) + borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) + disabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledFillColor) + disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) + disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) } public func encode(to encoder: Encoder) throws { @@ -68,8 +91,16 @@ public class ButtonModel: MoleculeModelProtocol { try container.encode(title, forKey: .title) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeModel(action, forKey: .action) + try container.encode(enabled, forKey: .enabled) try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(size, forKey: .size) + try container.encodeIfPresent(fillColor, forKey: .fillColor) + try container.encodeIfPresent(textColor, forKey: .textColor) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encodeIfPresent(disabledFillColor, forKey: .disabledFillColor) + try container.encodeIfPresent(disabledTextColor, forKey: .disabledTextColor) + try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor) try container.encodeIfPresent(required, forKey: .required) + try container.encodeIfPresent(requiredGroups, forKey: .requiredGroups) } } diff --git a/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift index 04b5aa07..defe6816 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift @@ -9,14 +9,14 @@ import Foundation import MVMCore -public class CaretLinkModel: MoleculeModelProtocol { +public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol { public static var identifier: String = "caretLink" public var backgroundColor: Color? public var title: String public var action: ActionModelProtocol public var enabledColor: Color = Color(uiColor: .black) public var disabledColor: Color? = Color(uiColor: .mfSilver()) - public var enabled: Bool = true + public var enabled = true public init(title: String, action: ActionModelProtocol) { self.title = title diff --git a/MVMCoreUI/Atoms/Buttons/Link.swift b/MVMCoreUI/Atoms/Buttons/Link.swift index 8cb9bb49..b4c00c94 100644 --- a/MVMCoreUI/Atoms/Buttons/Link.swift +++ b/MVMCoreUI/Atoms/Buttons/Link.swift @@ -46,7 +46,7 @@ import UIKit set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } - public static func estimatedHeight(forRow molecule: ModuleMoleculeModel?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + open override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 31.0 } } diff --git a/MVMCoreUI/Atoms/Buttons/LinkModel.swift b/MVMCoreUI/Atoms/Buttons/LinkModel.swift index 28e00d60..f4fab34c 100644 --- a/MVMCoreUI/Atoms/Buttons/LinkModel.swift +++ b/MVMCoreUI/Atoms/Buttons/LinkModel.swift @@ -8,7 +8,7 @@ import UIKit -public class LinkModel: MoleculeModelProtocol { +public class LinkModel: ButtonModelProtocol, MoleculeModelProtocol { public static var identifier: String = "link" public var backgroundColor: Color? public var title: String diff --git a/MVMCoreUI/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atoms/Buttons/PillButton.swift new file mode 100644 index 00000000..0d0a3090 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/PillButton.swift @@ -0,0 +1,179 @@ +// +// PillButton.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/28/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class PillButton: Button, MVMCoreUIViewConstrainingProtocol, FormValidationEnableDisableProtocol { + // Used to size the button. + var size = MVMCoreUIUtility.getWidth() + + var buttonModel: ButtonModel? { + get { return model as? ButtonModel } + } + + // Need to re-style on set. + open override var isEnabled: Bool { + didSet { + style() + } + } + + private enum ButtonHeight: CGFloat { + case tiny = 20 + case standard = 42 + } + + /// The primary styling for a button. Should be used for main buttons + public func stylePrimary() { + setTitleColor(.white, for: .normal) + setTitleColor(.white, for: .disabled) + layer.borderWidth = 0 + if isEnabled { + backgroundColor = .black + } else { + backgroundColor = .mvmCoolGray6 + } + } + + /// The secondary styling for a button. Should be used for secondary buttons + public func styleSecondary() { + setTitleColor(.black, for: .normal) + setTitleColor(.mvmCoolGray6, for: .disabled) + backgroundColor = .clear + layer.borderWidth = 1 + if isEnabled { + layer.borderColor = UIColor.black.cgColor + } else { + layer.borderColor = UIColor.mvmCoolGray6.cgColor + } + } + + /// Styles the button based on the model style + private func style() { + switch buttonModel?.style { + case .secondary: + styleSecondary() + default: + stylePrimary() + } + if let titleColor = buttonModel?.textColor { + setTitleColor(titleColor.uiColor, for: .normal) + } + if let disabledTitleColor = buttonModel?.disabledTextColor { + setTitleColor(disabledTitleColor.uiColor, for: .disabled) + } + if isEnabled { + if let fillColor = buttonModel?.fillColor { + backgroundColor = fillColor.uiColor + } + if let borderColor = buttonModel?.borderColor { + layer.borderWidth = 1 + layer.borderColor = borderColor.cgColor + } + } else { + if let fillColor = buttonModel?.disabledFillColor { + backgroundColor = fillColor.uiColor + } + if let borderColor = buttonModel?.disabledBorderColor { + layer.borderWidth = 1 + layer.borderColor = borderColor.cgColor + } + } + } + + private func getInnerPadding() -> CGFloat { + return getHeight() / 2.0 + } + + private func getHeight() -> CGFloat { + PillButton.getHeight(for: buttonModel?.size, size: size) + } + + public static func getHeight(for buttonSize: ButtonSize?, size: CGFloat) -> CGFloat { + switch buttonSize { + case .tiny: + return MFSizeObject(standardSize: ButtonHeight.tiny.rawValue, standardiPadPortraitSize: 34, iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? 20 + default: + return MFSizeObject(standardSize: ButtonHeight.standard.rawValue, standardiPadPortraitSize: 46, iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? 42 + } + } + + private func getMinimumWidth() -> CGFloat { + switch buttonModel?.size { + case .tiny: + return MFSizeObject(standardSize: 49.0, standardiPadPortraitSize: 90.0, iPadProLandscapeSize: 135.0)?.getValueBased(onSize: size) ?? 49.0 + default: + return MFSizeObject(standardSize: 102.0, standardiPadPortraitSize: 136.0, iPadProLandscapeSize: 153.0)?.getValueBased(onSize: size) ?? 102.0 + } + } + + open override var intrinsicContentSize: CGSize { + let size = super.intrinsicContentSize + let width = size.width + (2 * getInnerPadding()) + return CGSize(width: max(width, getMinimumWidth()), height: getHeight()) + } + + // MARK: - ModelMoleculeViewProtocol + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + // The button will get styled in the enable check in super. + super.setWithModel(model, delegateObject, additionalData) + + guard let model = model as? ButtonModel else { return } + setTitle(model.title, for: .normal) + + if let required = model.required, + required { + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + } + } + + open override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + PillButton.getHeight(for: (molecule as? ButtonModel)?.size, size: MVMCoreUIUtility.getWidth()) + } + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + self.size = size + invalidateIntrinsicContentSize() + switch buttonModel?.size { + case .tiny: + titleLabel?.font = MFFonts.mfFont75Bd(11 * (intrinsicContentSize.height / ButtonHeight.tiny.rawValue)) + default: + titleLabel?.font = MFFonts.mfFont75Bd(13 * (intrinsicContentSize.height / ButtonHeight.standard.rawValue)) + } + layer.cornerRadius = getInnerPadding() + } + + open override func setupView() { + super.setupView() + titleLabel?.numberOfLines = 1 + titleLabel?.lineBreakMode = .byTruncatingTail + titleLabel?.textAlignment = .center + contentHorizontalAlignment = .center + stylePrimary() + } + + // MARK: - MVMCoreUIViewConstrainingProtocol + open func horizontalAlignment() -> UIStackView.Alignment { + return .center + } + + // MARK: - FormValidationEnableDisableProtocol + public func isValidationRequired() -> Bool { + return buttonModel?.required ?? false + } + + public func requiredGroups() -> [String]? { + return buttonModel?.requiredGroups + } + + public func enableField(_ enable: Bool) { + isEnabled = isValidationRequired() ? enable : true + } +} diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 0a382705..a780151a 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -91,6 +91,10 @@ public typealias ButtonAction = (Button) -> () if let backgroundColor = model?.backgroundColor { self.backgroundColor = backgroundColor.uiColor } + + guard let model = model as? ButtonModelProtocol else { return } + isEnabled = model.enabled + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { diff --git a/MVMCoreUI/BaseClasses/ButtonModelProtocol.swift b/MVMCoreUI/BaseClasses/ButtonModelProtocol.swift new file mode 100644 index 00000000..d16c2464 --- /dev/null +++ b/MVMCoreUI/BaseClasses/ButtonModelProtocol.swift @@ -0,0 +1,14 @@ +// +// ButtonModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/28/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ButtonModelProtocol: EnableableModelProtocol { + var enabled: Bool { get set } + var action: ActionModelProtocol { get set } +} diff --git a/MVMCoreUI/Atoms/Buttons/ButtonView.swift b/MVMCoreUI/Legacy/Views/ButtonView.swift similarity index 100% rename from MVMCoreUI/Atoms/Buttons/ButtonView.swift rename to MVMCoreUI/Legacy/Views/ButtonView.swift diff --git a/MVMCoreUI/Models/ModelProtocols/DisableableModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/DisableableModelProtocol.swift new file mode 100644 index 00000000..1e49bd8c --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/DisableableModelProtocol.swift @@ -0,0 +1,13 @@ +// +// EnableableModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/28/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol EnableableModelProtocol { + var enabled: Bool { get set } +} diff --git a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift index 46139a37..8c813d24 100644 --- a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift @@ -60,7 +60,7 @@ import UIKit bottomSeparatorView?.setStyle(.none) } - public override static func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 80 } } diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index 12a1b1bd..d3523ccf 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -27,7 +27,7 @@ import Foundation ModelRegistry.register(LabelAttributeActionModel.self) // Buttons - MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: PrimaryButton.self, viewModelClass: ButtonModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: PillButton.self, viewModelClass: ButtonModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TwoButtonView.self, viewModelClass: TwoButtonViewModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Link.self, viewModelClass: LinkModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CaretButton.self, viewModelClass: CaretLinkModel.self)