diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 65c0e81d..fceb829b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -62,6 +62,8 @@ 01EB369323609801006832FA /* HeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368C23609801006832FA /* HeaderModel.swift */; }; 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; }; 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; }; + 0A0B147924ACFD8300BADD56 /* OrderTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0B147824ACFD8200BADD56 /* OrderTracker.swift */; }; + 0A0B147B24ACFDAD00BADD56 /* OrderTrackerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0B147A24ACFDAD00BADD56 /* OrderTrackerModel.swift */; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; }; 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.swift */; }; @@ -109,6 +111,9 @@ 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; + 0AC16CEB24B3A1080085EF34 /* Step.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC16CEA24B3A1080085EF34 /* Step.swift */; }; + 0AC16CED24B3A11C0085EF34 /* StepModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC16CEC24B3A11C0085EF34 /* StepModel.swift */; }; + 0AC16CEE24B3A39C0085EF34 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7918F423F5E7EA00772FF4 /* ImageView.swift */; }; 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; 0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BAE23FEF956004C5109 /* ExternalLink.swift */; }; 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB223FF0934004C5109 /* ExternalLinkModel.swift */; }; @@ -522,6 +527,8 @@ 01EB368C23609801006832FA /* HeaderModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderModel.swift; sourceTree = ""; }; 01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = ""; }; 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = ""; }; + 0A0B147824ACFD8200BADD56 /* OrderTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTracker.swift; sourceTree = ""; }; + 0A0B147A24ACFDAD00BADD56 /* OrderTrackerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTrackerModel.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = ""; }; 0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; @@ -572,6 +579,8 @@ 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPickerView+Extension.swift"; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; + 0AC16CEA24B3A1080085EF34 /* Step.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Step.swift; sourceTree = ""; }; + 0AC16CEC24B3A11C0085EF34 /* StepModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepModel.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 0AE98BAE23FEF956004C5109 /* ExternalLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLink.swift; sourceTree = ""; }; 0AE98BB223FF0934004C5109 /* ExternalLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLinkModel.swift; sourceTree = ""; }; @@ -1037,6 +1046,17 @@ path = Views; sourceTree = ""; }; + 0AC16CE924B3A0ED0085EF34 /* Order Tracker */ = { + isa = PBXGroup; + children = ( + 0A0B147A24ACFDAD00BADD56 /* OrderTrackerModel.swift */, + 0A0B147824ACFD8200BADD56 /* OrderTracker.swift */, + 0AC16CEC24B3A11C0085EF34 /* StepModel.swift */, + 0AC16CEA24B3A1080085EF34 /* Step.swift */, + ); + path = "Order Tracker"; + sourceTree = ""; + }; 0AE98BAD23FEF92B004C5109 /* Link */ = { isa = PBXGroup; children = ( @@ -1263,10 +1283,11 @@ C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */, D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */, 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, - EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */, EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */, - 0A775F2524893916009EFB58 /* ThreeHeadlineBodyLink.swift */, + EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */, 0A775F2724893937009EFB58 /* ThreeHeadlineBodyLinkModel.swift */, + 0A775F2524893916009EFB58 /* ThreeHeadlineBodyLink.swift */, + 0AC16CE924B3A0ED0085EF34 /* Order Tracker */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -2127,6 +2148,7 @@ 324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */, + 0A0B147924ACFD8300BADD56 /* OrderTracker.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, 943784F5236B77BB006A1E82 /* Wheel.swift in Sources */, @@ -2193,6 +2215,7 @@ 9445891F2385D2E900DE9FD4 /* CaretViewModel.swift in Sources */, 01C851D323CF9E740021F976 /* LabelToggleModel.swift in Sources */, 011D95A3240453F8000E3791 /* RuleRegexModel.swift in Sources */, + 0A0B147B24ACFDAD00BADD56 /* OrderTrackerModel.swift in Sources */, D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, @@ -2361,6 +2384,7 @@ 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, + 0AC16CED24B3A11C0085EF34 /* StepModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, @@ -2379,9 +2403,11 @@ 011D95A5240455DC000E3791 /* FormGroupRule.swift in Sources */, D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, + 0AC16CEB24B3A1080085EF34 /* Step.swift in Sources */, C7F8012123E8303200396FBD /* ListRVWheel.swift in Sources */, BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */, D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */, + 0AC16CEE24B3A39C0085EF34 /* ImageView.swift in Sources */, 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */, BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */, 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index 5a3a3a1f..09ff554f 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -11,7 +11,7 @@ import Foundation @objcMembers public class MoleculeObjectMapping: NSObject { public var moleculeMapping: [String: MoleculeViewProtocol.Type] = [:] - + /// Returns the mapping object stored in the singleton public static func shared() -> Self? { return MVMCoreActionUtility.initializerClassCheck(CoreUIObject.sharedInstance()?.moleculeMap, classToVerify: self) as? Self @@ -22,12 +22,12 @@ import Foundation try? ModelRegistry.register(viewModelClass) moleculeMapping.updateValue(viewClass, forKey: viewModelClass.identifier) } - + /// Returns the type of molecule view for the given model public func getMoleculeClass(_ model: MoleculeModelProtocol) -> MoleculeViewProtocol.Type? { return moleculeMapping[model.moleculeName] } - + /// Creates a molecule with the given model. public func createMolecule(_ model: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> MoleculeViewProtocol? { guard let type = moleculeMapping[model.moleculeName] else { return nil } @@ -38,7 +38,7 @@ import Foundation public func getMoleculeModelForJSON(_ json: [String: Any]) throws -> MoleculeModelProtocol? { guard let moleculeName = json.optionalStringForKey(KeyMoleculeName), let type = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) else { - throw ModelRegistry.Error.decoderErrorModelNotMapped() + throw ModelRegistry.Error.decoderErrorModelNotMapped() } guard let model = try type.decode(jsonDict: json) as? MoleculeModelProtocol else { throw ModelRegistry.Error.decoderError @@ -87,9 +87,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: RadioSwatches.self, viewModelClass: RadioSwatchesModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tags.self, viewModelClass: TagsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tag.self, viewModelClass: TagModel.self) - - - + // MARK:- Other Atoms MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self) @@ -119,7 +117,9 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyButton.self, viewModelClass: HeadlineBodyButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: BGImageHeadlineBodyButton.self, viewModelClass: BGImageHeadlineBodyButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ThreeHeadlineBodyLink.self, viewModelClass: ThreeHeadlineBodyLinkModel.self) - + MoleculeObjectMapping.shared()?.register(viewClass: OrderTracker.self, viewModelClass: OrderTrackerModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Step.self, viewModelClass: StepModel.self) + // MARK:- Left Right Molecules MoleculeObjectMapping.shared()?.register(viewClass: CornerLabels.self, viewModelClass: CornerLabelsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: LeftRightLabelView.self, viewModelClass: LeftRightLabelModel.self) @@ -127,7 +127,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyToggle.self, viewModelClass: HeadlineBodyToggleModel.self) MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyLinkToggle.self, viewModelClass: HeadlineBodyLinkToggleModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ActionDetailWithImage.self, viewModelClass: ActionDetailWithImageModel.self) - + // MARK:- List items MoleculeObjectMapping.shared()?.register(viewClass: MoleculeTableViewCell.self, viewModelClass: MoleculeListItemModel.self) MoleculeObjectMapping.shared()?.register(viewClass: DropDownFilterTableViewCell.self, viewModelClass: DropDownListItemModel.self) @@ -140,8 +140,8 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: StackItem.self, viewModelClass: StackItemModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: MoleculeCollectionItemModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CarouselItem.self, viewModelClass: CarouselItemModel.self) - - + + // MARK:- Other Container Molecules MoleculeObjectMapping.shared()?.register(viewClass: MoleculeContainer.self, viewModelClass: MoleculeContainerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self) @@ -149,7 +149,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ModuleMolecule.self, viewModelClass: ModuleMoleculeModel.self) MoleculeObjectMapping.shared()?.register(viewClass: BGImageMolecule.self, viewModelClass: BGImageMoleculeModel.self) - + // MARK:- Other Molecules MoleculeObjectMapping.shared()?.register(viewClass: DoughnutChartView.self, viewModelClass: DoughnutChartModel.self) @@ -157,7 +157,7 @@ import Foundation try? ModelRegistry.register(NavigationItemModel.self) try? ModelRegistry.register(NavigationImageButtonModel.self) try? ModelRegistry.register(NavigationLabelButtonModel.self) - + // MARK:- Other Organisms MoleculeObjectMapping.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self) MoleculeObjectMapping.shared()?.register(viewClass: BarsIndicatorView.self, viewModelClass: BarsCarouselIndicatorModel.self) @@ -204,7 +204,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnSpeedTestDivider.self, viewModelClass: ListThreeColumnSpeedTestDividerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnBillChangesDivider.self, viewModelClass: ListThreeColumnBillChangesDividerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnDataUsageDivider.self, viewModelClass: ListThreeColumnDataUsageDividerModel.self) - + // MARK:- Designed Headers MoleculeObjectMapping.shared()?.register(viewClass: HeadersH1Button.self, viewModelClass: HeadersH1ButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: HeadersH1LandingPageHeader.self, viewModelClass: HeadersH1LandingPageHeaderModel.self) @@ -218,10 +218,10 @@ import Foundation // MARK:- Device Items MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexButtonMedium.self, viewModelClass: ListDeviceComplexButtonMediumModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexButtonSmall.self, viewModelClass: ListDeviceComplexButtonSmallModel.self) - + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkSmall.self, viewModelClass: ListDeviceComplexLinkSmallModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkMedium.self, viewModelClass: ListDeviceComplexLinkMediumModel.self) - + // MARK:- Helper models try? ModelRegistry.register(RuleRequiredModel.self) try? ModelRegistry.register(RuleAnyRequiredModel.self) @@ -245,7 +245,7 @@ import Foundation guard let model = model else { return nil } return MoleculeObjectMapping.shared()?.getMoleculeClass(model)?.requiredModules(with: model, delegateObject, error: error) } - + /// Convenience function to add require modules for the given model to the passed in array. public static func addRequiredModules(for model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, moduleList: inout [String]?, errorList: inout [MVMCoreErrorObject]?) { guard let model = model else { return } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTracker.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTracker.swift new file mode 100644 index 00000000..076e77fe --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTracker.swift @@ -0,0 +1,133 @@ +// +// OrderTracker.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 7/1/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class OrderTracker: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var steps = [Step]() + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + isOpaque = false + } + + open override func reset() { + super.reset() + + resetSteps() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func constrain(stepModels: [StepModel]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + + resetSteps() + + guard let stepModels = stepModels else { return } + + var anchor = topAnchor + + for stepModel in stepModels { + + let step = Step() + step.set(with: stepModel, delegateObject, additionalData) + addSubview(step) + steps.append(step) + + step.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + trailingAnchor.constraint(equalTo: step.trailingAnchor).isActive = true + step.topAnchor.constraint(equalTo: anchor).isActive = true + + anchor = step.bottomAnchor + } + + bottomAnchor.constraint(equalTo: anchor).isActive = true + (subviews.last as? Step)?.heightConstraint?.isActive = false + } + + func resetSteps() { + + steps.forEach { + $0.reset() + $0.removeFromSuperview() + } + + steps = [] + } + + //-------------------------------------------------- + // MARK: - Draw + //-------------------------------------------------- + + open override func draw(_ rect: CGRect) { + + guard let context = UIGraphicsGetCurrentContext(), + let firstStep = steps.first + else { return } + + context.setLineWidth(1) + let imageDimension = firstStep.stateImage.bounds.height + let halfDimension = imageDimension / 2 + let startPoint = CGPoint(x: halfDimension, y: halfDimension) + let defaultGrey: UIColor = .mvmCoolGray3 + context.move(to: startPoint) + + var lineColor = (firstStep.state?.color() ?? defaultGrey).cgColor + + for (i, step) in steps.dropFirst().enumerated() { + + if let state = step.state, state == .incomplete { + lineColor = defaultGrey.cgColor + } + + context.setStrokeColor(lineColor) + let relativeRect = convert(step.stateImage.frame, from: step.stateImage) + let point = CGPoint(x: halfDimension, y: relativeRect.midY) + + context.addLine(to: point) + context.strokePath() + + // Break out of loop since we're on the last step. + if i == steps.count - 2 { + break + } + + lineColor = (step.state?.color() ?? defaultGrey).cgColor + context.move(to: point) + } + } + + //------------------------------------------------------ + // MARK: - MoleculeViewProtocol + //------------------------------------------------------ + + public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 196 + } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + + guard let model = model as? OrderTrackerModel else { return } + + constrain(stepModels: model.steps, delegateObject: delegateObject, additionalData: additionalData) + setNeedsLayout() + setNeedsDisplay() + } +} diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTrackerModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTrackerModel.swift new file mode 100644 index 00000000..fee4ce97 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/OrderTrackerModel.swift @@ -0,0 +1,48 @@ +// +// OrderTrackerModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 7/1/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class OrderTrackerModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var backgroundColor: Color? + + public static var identifier: String = "orderTracker" + public var steps: [StepModel] + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case steps + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + steps = try typeContainer.decode([StepModel].self, forKey: .steps) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(steps, forKey: .steps) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/Step.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/Step.swift new file mode 100644 index 00000000..1d53d81a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/Step.swift @@ -0,0 +1,114 @@ +// +// Step.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 7/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class Step: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var headline = Label(fontStyle: .BoldMicro) + public var bodyTop = Label(fontStyle: .RegularMicro) + public var bodyBottom = Label(fontStyle: .RegularMicro) + public var stateImage = ImageView() + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + public var stepModel: StepModel? { + return model as? StepModel + } + + public var state: StepModel.State? { + return stepModel?.state + } + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + + addSubview(stateImage) + addSubview(headline) + addSubview(bodyTop) + addSubview(bodyBottom) + + stateImage.contentMode = .scaleAspectFit + stateImage.layer.backgroundColor = UIColor.white.cgColor + stateImage.topAnchor.constraint(equalTo: topAnchor).isActive = true + stateImage.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + stateImage.heightAnchor.constraint(equalToConstant: 18).isActive = true + stateImage.widthAnchor.constraint(equalToConstant: 18).isActive = true + + headline.topAnchor.constraint(equalTo: topAnchor, constant: 2).isActive = true + headline.leadingAnchor.constraint(equalTo: stateImage.trailingAnchor, constant: Padding.Four).isActive = true + trailingAnchor.constraint(equalTo: headline.trailingAnchor).isActive = true + + bodyTop.topAnchor.constraint(equalTo: headline.bottomAnchor).isActive = true + bodyTop.leadingAnchor.constraint(equalTo: stateImage.trailingAnchor, constant: Padding.Four).isActive = true + trailingAnchor.constraint(equalTo: bodyTop.trailingAnchor).isActive = true + + bodyBottom.topAnchor.constraint(equalTo: bodyTop.bottomAnchor).isActive = true + bodyBottom.leadingAnchor.constraint(equalTo: stateImage.trailingAnchor, constant: Padding.Four).isActive = true + trailingAnchor.constraint(equalTo: bodyBottom.trailingAnchor).isActive = true + let bodyBottomConstraint = bottomAnchor.constraint(greaterThanOrEqualTo: bodyBottom.bottomAnchor) + bodyBottomConstraint.priority = .defaultHigh + bodyBottomConstraint.isActive = true + + heightConstraint = heightAnchor.constraint(equalToConstant: 58) + heightConstraint?.priority = .defaultLow + heightConstraint?.isActive = true + } + + public override func reset() { + super.reset() + + headline.reset() + bodyTop.reset() + bodyBottom.reset() + stateImage.reset() + heightConstraint?.isActive = true + } + + //------------------------------------------------------ + // MARK: - MoleculeViewProtocol + //------------------------------------------------------ + + public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 64 + } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + + guard let model = model as? StepModel else { return } + + headline.set(with: model.headline, delegateObject, additionalData) + + if let bodyTopModel = model.bodyTop { + bodyTop.set(with: bodyTopModel, delegateObject, additionalData) + } + + if let bodyBottomModel = model.bodyBottom { + bodyBottom.set(with: bodyBottomModel, delegateObject, additionalData) + } + + stateImage.image = model.state?.image() + } +} diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/StepModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/StepModel.swift new file mode 100644 index 00000000..f4661945 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Order Tracker/StepModel.swift @@ -0,0 +1,94 @@ +// +// StepModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 7/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class StepModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var backgroundColor: Color? + + public static var identifier: String = "step" + public var state: State? + public var headline: LabelModel + public var bodyTop: LabelModel? + public var bodyBottom: LabelModel? + + //-------------------------------------------------- + // MARK: - Enum + //-------------------------------------------------- + + public enum State: String, Codable { + case complete + case incomplete + case invalid + + func image() -> UIImage? { + + switch self { + case .complete: + return MVMCoreUIUtility.imageNamed("icon_tracker_complete") + + case .incomplete: + return MVMCoreUIUtility.imageNamed("icon_tracker_incomplete") + + case .invalid: + return MVMCoreUIUtility.imageNamed("icon_tracker_invalid") + } + } + + func color() -> UIColor { + + switch self { + case .complete: + return .mvmGreen + + case .incomplete, .invalid: + return .mvmCoolGray3 + } + } + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case state + case headline + case bodyTop + case bodyBottom + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + state = try typeContainer.decodeIfPresent(State.self, forKey: .state) + headline = try typeContainer.decode(LabelModel.self, forKey: .headline) + bodyTop = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .bodyTop) + bodyBottom = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .bodyBottom) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(headline, forKey: .headline) + try container.encodeIfPresent(bodyTop, forKey: .bodyTop) + try container.encodeIfPresent(bodyBottom, forKey: .bodyBottom) + } +} diff --git a/MVMCoreUI/BaseClasses/ImageView.swift b/MVMCoreUI/BaseClasses/ImageView.swift index aa004d27..2acfc5ae 100644 --- a/MVMCoreUI/BaseClasses/ImageView.swift +++ b/MVMCoreUI/BaseClasses/ImageView.swift @@ -8,7 +8,7 @@ import UIKit -open class ImageView: UIImageView, ModelMoleculeViewProtocol { +open class ImageView: UIImageView, MoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -51,13 +51,25 @@ open class ImageView: UIImageView, ModelMoleculeViewProtocol { } } + /// Will be called only once. + open func setupView() { + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open func reset() { + image = nil + backgroundColor = .clear + } + //-------------------------------------------------- - // MARK: - ModelMoleculeViewProtocol + // MARK: - MoleculeViewProtocol //-------------------------------------------------- - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.model = model - if let backgroundColor = model?.backgroundColor { + if let backgroundColor = model.backgroundColor { self.backgroundColor = backgroundColor.uiColor } } @@ -73,27 +85,8 @@ open class ImageView: UIImageView, ModelMoleculeViewProtocol { open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { return nil } -} - -// MARK:- MVMCoreViewProtocol -extension ImageView: MVMCoreViewProtocol { open func updateView(_ size: CGFloat) { } - /// Will be called only once. - open func setupView() { - translatesAutoresizingMaskIntoConstraints = false - insetsLayoutMarginsFromSafeArea = false - MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) - } -} - -// MARK:- MVMCoreUIMoleculeViewProtocol -extension ImageView: MVMCoreUIMoleculeViewProtocol { - - open func reset() { - backgroundColor = .clear - } - open func setAsMolecule() { } } diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Contents.json new file mode 100644 index 00000000..23859fda --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Green.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Green@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Green@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green.png new file mode 100644 index 00000000..285005cb Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@2x.png new file mode 100644 index 00000000..30bd32f2 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@3x.png new file mode 100644 index 00000000..183d6022 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_complete.imageset/Green@3x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Contents.json new file mode 100644 index 00000000..e8f406a7 --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Empty state icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Empty state icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Empty state icon@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon.png new file mode 100644 index 00000000..9623a477 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@2x.png new file mode 100644 index 00000000..cac4cbef Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@3x.png new file mode 100644 index 00000000..e01da304 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_incomplete.imageset/Empty state icon@3x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon.png new file mode 100644 index 00000000..4169bd01 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@2x.png new file mode 100644 index 00000000..65133b98 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@3x.png new file mode 100644 index 00000000..d8b628ef Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Alert icon@3x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Contents.json new file mode 100644 index 00000000..2444fd17 --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/icon_tracker_invalid.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Alert icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Alert icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Alert icon@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file