diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e51e8336..b263a72f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -146,6 +146,8 @@ 32D2609724C19E2100B56344 /* LockupsPlanSMLXLModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D2609524C19E2100B56344 /* LockupsPlanSMLXLModel.swift */; }; 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */; }; 32F8804824765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */; }; + 444FB7C12821B73200DFE692 /* TitleLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C02821B73200DFE692 /* TitleLockup.swift */; }; + 444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */; }; 4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */; }; 522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */; }; 522679C223FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */; }; @@ -735,6 +737,8 @@ 32D2609524C19E2100B56344 /* LockupsPlanSMLXLModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockupsPlanSMLXLModel.swift; sourceTree = ""; }; 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinksModel.swift; sourceTree = ""; }; 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinks.swift; sourceTree = ""; }; + 444FB7C02821B73200DFE692 /* TitleLockup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockup.swift; sourceTree = ""; }; + 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockupModel.swift; sourceTree = ""; }; 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageRenderingMode+Extension.swift"; sourceTree = ""; }; 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = ""; }; 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinksModel.swift; sourceTree = ""; }; @@ -1434,6 +1438,8 @@ 525019DC2406430800EED91C /* ListProgressBarData.swift */, AA45AA0A24BF0263007A6EA7 /* LockUpsPlanNamesModel.swift */, AA45AA0C24BF0276007A6EA7 /* LockUpsPlanNames.swift */, + 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */, + 444FB7C02821B73200DFE692 /* TitleLockup.swift */, ); path = LockUps; sourceTree = ""; @@ -2667,6 +2673,7 @@ 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, 3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */, AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */, + 444FB7C12821B73200DFE692 /* TitleLockup.swift in Sources */, D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */, AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, @@ -2860,6 +2867,7 @@ 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 32D2609624C19E2100B56344 /* LockupsPlanSMLXL.swift in Sources */, 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, + 444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */, D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */, D260105323CEA61600764D80 /* ToggleModel.swift in Sources */, 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift new file mode 100644 index 00000000..b48e4ddb --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift @@ -0,0 +1,123 @@ +// +// TitleLockup.swift +// MVMCoreUI +// +// Created by Nadigadda, Sumanth on 04/05/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +@objcMembers open class TitleLockup: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let eyebrow = Label(fontStyle: .RegularBodySmall) + public let title = Label(fontStyle: .RegularBodySmall) + public let subTitle = Label(fontStyle: .RegularBodySmall) + public lazy var stack: UIStackView = { + let stack = UIStackView(arrangedSubviews: [eyebrow, title, subTitle]) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + return stack + }() + + var castModel: TitleLockupModel? { + get { return model as? TitleLockupModel } + } + + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + + public convenience init() { + self.init(frame: .zero) + } + + //-------------------------------------------------- + // MARK: - MFViewProtocol + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + addSubview(stack) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + stack.updateView(size) + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func reset() { + super.reset() + stack.reset() + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? TitleLockupModel else { return } + stack.setCustomSpacing(model.defaultEyebrowTitleSpacing(), after: eyebrow) + stack.setCustomSpacing(model.defaultTitleSubTitleSpacing(), after: title) + stack.updateContainedMolecules(with: [model.eyebrow, + model.title, + model.subTitle], + delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 65 + } + + //-------------------------------------------------- + // MARK: - Accessibility Helpers + //-------------------------------------------------- + + /// Returns the labels text in one message. + func getAccessibilityMessage() -> String? { + + var message = "" + + if let eyebrowLabel = eyebrow.text { + message += eyebrowLabel + ", " + } + + if let headlineLabel = title.text { + message += headlineLabel + ", " + } + + if let bodyLabel = subTitle.text { + message += bodyLabel + } + + return message.count > 0 ? message : nil + } + + /// Returns an array of the appropriate accessibility elements. + func getAccessibilityElements() -> [Any]? { + + var elements: [UIView] = [] + + if eyebrow.hasText { + elements.append(eyebrow) + } + + if title.hasText { + elements.append(title) + } + + if subTitle.hasText { + elements.append(subTitle) + } + + return elements.count > 0 ? elements : nil + } +} + diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift new file mode 100644 index 00000000..4548207d --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -0,0 +1,215 @@ +// +// TitleLockupModel.swift +// MVMCoreUI +// +// Created by Nadigadda, Sumanth on 04/05/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import VDSColorTokens + +public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "titleLockup" + public var moleculeName: String = TitleLockupModel.identifier + + public var eyebrow: LabelModel? + public var title: LabelModel + public var subTitle: LabelModel? + + public var alignment: Alignment = .left { + didSet { + ///Updating the text alignment for all labels + if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) { + eyebrow?.textAlignment = textAlignment + title.textAlignment = textAlignment + subTitle?.textAlignment = textAlignment + } + } + } + + public var inverted: Bool = false { + didSet { + ///Updating the text color + eyebrow?.textColor = titleColor + title.textColor = titleColor + subTitle?.textColor = subTitleColor + } + } + + private var _backgroundColor: Color? + public var backgroundColor: Color? { + get { + return inverted ? Color(uiColor: VDSColor.backgroundPrimaryDark) : Color(uiColor: VDSColor.backgroundPrimaryLight) + } + set { + _backgroundColor = newValue + } + } + + public var titleColor: Color? { + return inverted ? Color(uiColor: VDSColor.elementsPrimaryOndark) : Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + + public var subTitleColor: Color? { + return inverted ? Color(uiColor: VDSColor.elementsSecondaryOndark) : Color(uiColor: VDSColor.elementsSecondaryOnlight) + } + + public var children: [MoleculeModelProtocol] { + [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(eyebrow: LabelModel? = nil, title: LabelModel, subTitle: LabelModel? = nil) throws { + self.eyebrow = eyebrow + self.title = title + self.subTitle = subTitle + updateLabelAttributes() + } + + //-------------------------------------------------- + // MARK: - Enum + //-------------------------------------------------- + + public enum Alignment: String, Codable { + case left + case center + } + + //-------------------------------------------------- + // MARK: - Styling + //-------------------------------------------------- + + /// Returns the default fontStyle for the subtitle, based on the title fontStyle. + func defaultSubtitleFontStyle() -> Styler.Font { + switch title.fontStyle { + case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall: + return .RegularBodyLarge + case .RegularFeatureSmall, .RegularFeatureMedium: + return .RegularTitleLarge + default: + return .RegularBodySmall + } + } + + /// Returns the default spacing between the eyebrow and title, based on the title fontStyle. + func defaultEyebrowTitleSpacing() -> CGFloat { + switch title.fontStyle { + case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall: + return Padding.Three + case .RegularFeatureMedium: + return subTitle?.fontStyle == .RegularBodyLarge ? Padding.Three : Padding.Four + default: + return Padding.Two + } + } + + /// Returns the default spacing between the title and subTitle, based on the title fontStyle. + func defaultTitleSubTitleSpacing() -> CGFloat { + switch title.fontStyle { + case .RegularTitleXLarge: + return Padding.Three + case .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall: + return Padding.Four + case .RegularFeatureMedium: + return Padding.Five + default: + return Padding.Two + } + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case eyebrow + case title + case subTitle + case inverted + case alignment + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + title = try typeContainer.decodeMolecule(codingKey: .title) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) + + if let newAlignment = try typeContainer.decodeIfPresent(Alignment.self, forKey: .alignment) { + alignment = newAlignment + } + + if let invertedStatus = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + inverted = invertedStatus + } + + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + + updateLabelAttributes() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeModel(title, forKey: .title) + try container.encodeIfPresent(subTitle, forKey: .subTitle) + try container.encode(alignment, forKey: .alignment) + try container.encode(inverted, forKey: .inverted) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) + } + + //-------------------------------------------------- + // MARK: - Model updates + //-------------------------------------------------- + + private func updateLabelAttributes() { + // If subtitle style is not available, will set font style based on the component + if subTitle?.fontStyle == nil { + subTitle?.fontStyle = defaultSubtitleFontStyle() + } + + // If eyebrow style is not available, will set font style based on the component. Eyebrow and subtitle share the same font size + if eyebrow?.fontStyle == nil { + eyebrow?.fontStyle = subTitle?.fontStyle + } + + // Updating the text color + if eyebrow?.textColor == nil { + eyebrow?.textColor = subTitleColor + } + if title.textColor == nil { + title.textColor = titleColor + } + if subTitle?.textColor == nil { + subTitle?.textColor = subTitleColor + } + + // Updating the text alignment for all labels + if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) { + if eyebrow?.textAlignment == nil { + eyebrow?.textAlignment = textAlignment + } + if title.textAlignment == nil { + title.textAlignment = textAlignment + } + if subTitle?.textAlignment == nil { + subTitle?.textAlignment = textAlignment + } + } + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 2a7c9d90..ef051b88 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -204,6 +204,7 @@ open class CoreUIModelMapping: ModelMapping { // MARK:- LockUps ModelRegistry.register(handler: LockUpsPlanNames.self, for: LockUpsPlanNamesModel.self) ModelRegistry.register(handler: LockupsPlanSMLXL.self, for: LockupsPlanSMLXLModel.self) + ModelRegistry.register(handler: TitleLockup.self, for: TitleLockupModel.self) // MARK: - Top Notifications ModelRegistry.register(handler: NotificationView.self, for: NotificationModel.self)