diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 178c309e..abdc1242 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -32,7 +32,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW public var disabledFillColor: Color? public var disabledTextColor: Color? public var disabledBorderColor: Color? - public var groupName: String = FormValidator.defaultGroupName + public var groupName: String = "" public func setValidity(_ valid: Bool, group: FormGroupRule) { enabled = valid diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index da762dbb..785d6ae6 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -39,7 +39,7 @@ open class RadioBox: Control { super.setupView() layer.delegate = self - layer.borderColor = UIColor.black.cgColor + layer.borderColor = UIColor.mvmCoolGray6.cgColor layer.borderWidth = 1 label.numberOfLines = 1 @@ -60,7 +60,7 @@ open class RadioBox: Control { // MARK: - MoleculeViewProtocol - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? RadioBoxModel else { return } isSelected = model.selected @@ -69,6 +69,15 @@ open class RadioBox: Control { subTextLabel.text = model.subText isOutOfStock = model.strikethrough subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0 + if let color = model.selectedAccentColor?.uiColor { + accentColor = color + } + } + + open override func reset() { + super.reset() + backgroundColor = .white + accentColor = .mvmRed } // MARK: - State Handling @@ -77,11 +86,12 @@ open class RadioBox: Control { // Draw the strikethrough strikeLayer?.removeFromSuperlayer() if isOutOfStock { - let line = getStrikeThrough(color: .black, thickness: 1) + let line = getStrikeThrough(color: isSelected ? .black : .mvmCoolGray6, thickness: 1) layer.addSublayer(line) strikeLayer = line } + // Draw the border borderLayer?.removeFromSuperlayer() if isSelected { @@ -98,6 +108,7 @@ open class RadioBox: Control { if !isEnabled { let mask = getMaskLayer() layer.mask = mask + maskLayer = mask } } @@ -108,6 +119,7 @@ open class RadioBox: Control { } @objc open func selectBox() { + guard isEnabled else { return } isSelected = true radioBoxModel?.selected = isSelected layer.setNeedsDisplay() @@ -131,7 +143,7 @@ open class RadioBox: Control { let topLineLayer = CAShapeLayer() topLineLayer.fillColor = nil - topLineLayer.strokeColor = UIColor.mvmRed.cgColor + topLineLayer.strokeColor = accentColor.cgColor topLineLayer.lineWidth = 4 topLineLayer.path = topLinePath.cgPath layer.addSublayer(topLineLayer) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift index c90b5e92..c6f5474e 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift @@ -8,7 +8,12 @@ import Foundation open class RadioBoxCollectionViewCell: CollectionViewCell { - let radioBox = RadioBox() + public let radioBox = RadioBox() + + open override func reset() { + super.reset() + backgroundColor = .clear + } open override func setupView() { super.setupView() diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift index b2a23c6a..986eefac 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -11,8 +11,8 @@ import Foundation public static var identifier: String = "radioBox" public var text: String public var subText: String? - public var backgroundColor: Color? = Color(uiColor: .white) - public var selectedAccentColor = Color(uiColor: .mvmRed) + public var backgroundColor: Color? + public var selectedAccentColor: Color? public var selected: Bool = false public var enabled: Bool = true public var strikethrough: Bool = false @@ -34,12 +34,8 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) text = try typeContainer.decode(String.self, forKey: .text) subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText) - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { - selectedAccentColor = color - } - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { - backgroundColor = color - } + selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { selected = isSelected } @@ -58,7 +54,7 @@ import Foundation try container.encode(moleculeName, forKey: .moleculeName) try container.encode(text, forKey: .text) try container.encodeIfPresent(subText, forKey: .subText) - try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encode(selected, forKey: .selected) try container.encode(enabled, forKey: .enabled) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift index 6fe66bc2..2a6ba953 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift @@ -15,7 +15,11 @@ open class RadioBoxes: View { private let boxWidth: CGFloat = 151.0 private let boxHeight: CGFloat = 64.0 private let itemSpacing: CGFloat = 8.0 - + private var numberOfColumns: CGFloat = 2.0 + private var radioBoxesModel: RadioBoxesModel? { + return model as? RadioBoxesModel + } + private var delegateObject: MVMCoreUIDelegateObject? /// The models for the molecules. @@ -42,7 +46,7 @@ open class RadioBoxes: View { } // MARK: - MoleculeViewProtocol - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject @@ -94,7 +98,7 @@ open class RadioBoxes: View { } // Calculate the height - let rows = ceil(CGFloat(boxes.count) / 2.0) + let rows = ceil(CGFloat(boxes.count) / numberOfColumns) let height = (rows * boxHeight) + ((rows - 1) * itemSpacing) collectionViewHeight?.constant = height } @@ -102,7 +106,7 @@ open class RadioBoxes: View { extension RadioBoxes: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / 2 + let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / numberOfColumns return CGSize(width: itemWidth, height: boxHeight) } } @@ -117,7 +121,16 @@ extension RadioBoxes: UICollectionViewDataSource { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { fatalError() } + cell.reset() cell.radioBox.isUserInteractionEnabled = false + + if let color = radioBoxesModel?.boxesColor { + cell.radioBox.backgroundColor = color.uiColor + } + if let color = radioBoxesModel?.selectedAccentColor { + cell.radioBox.accentColor = color.uiColor + } + cell.set(with: molecule, delegateObject, nil) cell.updateView(size ?? collectionView.bounds.width) if molecule.selected { @@ -129,13 +142,18 @@ extension RadioBoxes: UICollectionViewDataSource { } extension RadioBoxes: UICollectionViewDelegate { - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + guard let molecule = boxes?[indexPath.row] else { return false } + return molecule.enabled + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } cell.radioBox.selectBox() _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } - public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } cell.radioBox.deselectBox() } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift index 28c4fab5..5b568910 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -9,9 +9,10 @@ import Foundation @objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol { public static var identifier: String = "radioBoxes" + public var boxes: [RadioBoxModel] public var backgroundColor: Color? public var selectedAccentColor: Color? - public var boxes: [RadioBoxModel] + public var boxesColor: Color? public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? @@ -28,6 +29,7 @@ import Foundation case moleculeName case selectedAccentColor case backgroundColor + case boxesColor case boxes case fieldKey case groupName @@ -37,6 +39,7 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + boxesColor = try typeContainer.decodeIfPresent(Color.self, forKey: .boxesColor) boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { diff --git a/MVMCoreUI/Atomic/Atoms/Views/MFLoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/MFLoadImageView.swift index b8b5321d..826759a7 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/MFLoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/MFLoadImageView.swift @@ -276,7 +276,7 @@ import UIKit let fallbackImageName = customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback") if let format = format, format.lowercased().contains("gif") { // Gifs aren't supported by default and need special handling - MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, completionHandler: finishedLoadingBlock) + MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock) } else { MVMCoreCache.shared()?.getImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock) } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index eb4be908..a49dfad3 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -266,9 +266,7 @@ import UIKit } // Handle data for first load. Dispatched to allow subclasses to finish their view did load implementations. - DispatchQueue.main.async { - self.handleNewDataAndUpdateUI() - } + self.handleNewDataAndUpdateUI() } open override func viewDidLayoutSubviews() { @@ -292,8 +290,10 @@ import UIKit open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - // Update the navigation bar ui when view is appearing - setNavigationController() + // Update the navigation bar ui when view is appearing. Can remove check in the future, see viewControllerReady + if manager == nil { + setNavigationController() + } } open override func viewDidAppear(_ animated: Bool) { @@ -326,6 +326,10 @@ import UIKit // MARK: - MVMCoreViewManagerViewControllerProtocol open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { + // TODO: This check and set aren't technically needed anymore. The one in viewwillappear should be enough. However, there is a timing issue with the manager where the screen lays out before the menu shows, so the screen grows off the screen. Can fix in the future. + if let _ = self.view { + setNavigationController() + } // Janky way to track current page. MVMCoreUISession.sharedGlobal()?.currentPageType = pageType MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self) diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 29690ae5..744304ba 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -89,7 +89,7 @@ import MVMCore // Validate each rule. var valid = true for rule in group.rules { - valid = valid && validateRule(rule) + valid = valid && rule.validate(fields) } // Notify the group watchers of validity. @@ -98,19 +98,6 @@ import MVMCore watcher.setValidity(valid, group: group) } } - - return valid - } - - /// Validates a given rule. Returns if valid. - public func validateRule(_ rule: RulesProtocol) -> Bool { - var valid = true - for formKey in rule.fields { - guard let formField = fields[formKey] else { continue } - let fieldValidity = rule.isValid(formField) - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: rule) - valid = valid && fieldValidity - } return valid } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift index d41c0b0c..6ca905bf 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift @@ -35,7 +35,7 @@ public class RuleAnyRequiredModel: RulesProtocol { return false } - public func isValid(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { + public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift index 7cf24cd5..450fdb2a 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift @@ -26,16 +26,13 @@ public class RuleAnyValueChangedModel: RulesProtocol { return formField.baseValue != formField.formFieldValue() } - public func isValid(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { - + public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } - if isValid(formField) { return true } - } - + } return false } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift index 05e7ad32..fb7585f2 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift @@ -26,8 +26,7 @@ public class RuleEqualsModel: RulesProtocol { return false } - public func isValid(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { - + public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { var valid = true var compareValue: AnyHashable? diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift index 6afe5b17..305b4c35 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift @@ -22,6 +22,9 @@ public protocol RulesProtocol: ModelProtocol { // Returns if a given field is valid according to the rule func isValid(_ formField: FormFieldProtocol) -> Bool + + // Validates the rule and returns the result. + func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool } public extension RulesProtocol { @@ -37,4 +40,16 @@ public extension RulesProtocol { static var categoryName: String { return "\(RulesProtocol.self)" } + + // Individual rule can override the function to validate based on the rule type. + func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { + var valid = true + for formKey in fields { + guard let formField = fieldMolecules[formKey] else { continue } + let fieldValidity = isValid(formField) + (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self) + valid = valid && fieldValidity + } + return valid + } }