diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift index 09068120..3f905a32 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift @@ -80,8 +80,6 @@ import UIKit heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) heightConstraint?.isActive = true isAccessibilityElement = true - accessibilityTraits = .button - updateAccessibilityLabel() } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { @@ -91,6 +89,8 @@ import UIKit guard let model = model as? HeartModel else { return } accessibilityIdentifier = model.accessibilityIdentifier isSelected = model.isActive + isEnabled = model.enabled + updateAccessibilityLabel() } //-------------------------------------------------- @@ -98,11 +98,13 @@ import UIKit //-------------------------------------------------- /// Adjust accessibility label based on selection of Heart. func updateAccessibilityLabel() { - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "heart_unfavorite_action_hint" : "heart_favorite_action_hint") + accessibilityHint = isEnabled ? MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "heart_unfavorite_action_hint" : "heart_favorite_action_hint") : nil + accessibilityTraits = isEnabled ? .button : .none accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "heart_selected_state" : "heart_not_selected_state") } func tapAction() { + guard isEnabled else { return } isSelected = !isSelected if let heartModel = heartModel { Button.performButtonAction(with: heartModel.action, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: heartModel) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift index 815117a9..aea065ef 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift @@ -7,7 +7,8 @@ // -open class HeartModel: MoleculeModelProtocol { +open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,6 +20,7 @@ open class HeartModel: MoleculeModelProtocol { public var activeColor: Color = Color(uiColor: .mvmRed) public var inActiveColor: Color = Color(uiColor: .clear) public var action: ActionModelProtocol = ActionNoopModel() + public var enabled: Bool = true //-------------------------------------------------- // MARK: - Keys @@ -32,6 +34,7 @@ open class HeartModel: MoleculeModelProtocol { case activeColor case inActiveColor case action + case enabled } //-------------------------------------------------- @@ -58,6 +61,9 @@ open class HeartModel: MoleculeModelProtocol { if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) { self.action = action } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } } public func encode(to encoder: Encoder) throws { @@ -69,5 +75,6 @@ open class HeartModel: MoleculeModelProtocol { try container.encode(activeColor, forKey: .activeColor) try container.encode(inActiveColor, forKey: .inActiveColor) try container.encodeModel(action, forKey: .action) + try container.encode(enabled, forKey: .enabled) } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift index 6a1f0dc5..d81ab5c8 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift @@ -42,8 +42,6 @@ addMolecule(stack) stack.restack() horizontalStack.restack() - accessibilityHint = heart.accessibilityHint - accessibilityTraits = heart.accessibilityTraits } public override func updateView(_ size: CGFloat) { @@ -123,20 +121,40 @@ } func updateAccessibilityLabel() { - if let accessoryView = accessoryView { - // Both caret and heart. + let hasHeart = !(horizontalStack.stackModel?.molecules[1].gone ?? true) + if let accessoryView = accessoryView, + hasHeart { + // Both accessory and heart actions. isAccessibilityElement = false accessoryView.accessibilityLabel = getAccessibilityMessage() accessibilityElements = [accessoryView, heart] } else { // Make whole cell focusable if no action. isAccessibilityElement = true - if let message = getAccessibilityMessage(), - let heartLabel = heart.accessibilityLabel { - accessibilityLabel = message + ", " + heartLabel + var message = getAccessibilityMessage() + if hasHeart { + accessibilityHint = heart.accessibilityHint + if let heartLabel = heart.accessibilityLabel { + message = (message ?? "") + ", " + heartLabel + } } else { - accessibilityLabel = getAccessibilityMessage() + accessibilityHint = nil } + accessibilityLabel = message } } + + // Ensures voice over does not read "selected" after user triggers action on cell. + override public var accessibilityTraits: UIAccessibilityTraits { + get { + if (accessoryView != nil) { + return .button + } else if (!(horizontalStack.stackModel?.molecules[1].gone ?? true)) { + return heart.accessibilityTraits + } else { + return .none + } + } + set {} + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift index 32392b50..10a0cd29 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift @@ -12,7 +12,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- public static var identifier = "listStoreLocator" - public var heart: HeartModel + public var heart: HeartModel? public var leftHeadline: LabelModel public var leftBody: LabelModel public var leftSubBody: LabelModel @@ -22,7 +22,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(heart: HeartModel, leftHeadline: LabelModel, leftBody: LabelModel, leftSubBody: LabelModel, rightLabel: LabelModel) { + public init(heart: HeartModel?, leftHeadline: LabelModel, leftBody: LabelModel, leftSubBody: LabelModel, rightLabel: LabelModel) { self.heart = heart self.leftHeadline = leftHeadline self.leftBody = leftBody @@ -59,7 +59,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - heart = try typeContainer.decode(HeartModel.self, forKey:.heart) + heart = try typeContainer.decodeIfPresent(HeartModel.self, forKey:.heart) leftHeadline = try typeContainer.decode(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decode(LabelModel.self, forKey: .leftBody) leftSubBody = try typeContainer.decode(LabelModel.self, forKey: .leftSubBody) @@ -71,7 +71,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encode(heart, forKey: .heart) + try container.encodeIfPresent(heart, forKey: .heart) try container.encode(leftHeadline, forKey: .leftHeadline) try container.encode(leftBody, forKey: .leftBody) try container.encode(leftSubBody, forKey: .leftSubBody) diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift index 77a5c350..f18c9fa1 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift @@ -16,6 +16,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule public static var identifier: String = "navigationImageButton" public var image: String public var action: ActionModelProtocol + public var accessibilityText: String? //-------------------------------------------------- // MARK: - Initializer @@ -35,6 +36,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule case action case accessibilityIdentifier case moleculeName + case accessibilityText } //-------------------------------------------------- @@ -46,6 +48,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) image = try typeContainer.decode(String.self, forKey: .image) action = try typeContainer.decodeModel(codingKey: .action) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) } open func encode(to encoder: Encoder) throws { @@ -54,6 +57,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeModel(action, forKey: .action) + try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) } //-------------------------------------------------- @@ -65,6 +69,11 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule let uiImage = MVMCoreCache.shared()?.getImageFromRegisteredBundles(image) let buttonItem = ImageBarButtonItem.create(with: uiImage, actionModel: action, delegateObject: delegateObject, additionalData: additionalData) buttonItem.accessibilityIdentifier = accessibilityIdentifier ?? image + if let accessibilityString = accessibilityText { + buttonItem.accessibilityLabel = accessibilityString + buttonItem.isAccessibilityElement = true + } + return buttonItem } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift index eeacb98d..874868e5 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift @@ -12,21 +12,24 @@ import Foundation public override class var identifier: String { return "numberedList" } + public var numberColor: Color private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor case list + case numberColor } // Numbered list model comes in the from of list = [MoleculeModelProtocol] public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - + + numberColor = try typeContainer.decodeIfPresent(Color.self, forKey: .numberColor) ?? Color(uiColor: .mvmBlack) let list: [MoleculeModelProtocol] = try typeContainer.decodeModels(codingKey: .list) var models: [MoleculeStackItemModel] = [] for (index, molecule) in list.enumerated() { - models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: "\(index+1).", molecule: molecule))) + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: "\(index+1).", molecule: molecule, stringColor: numberColor))) } super.init(molecules: models, spacing: 0) } @@ -41,6 +44,7 @@ import Foundation models.append(((molecule as! MoleculeStackItemModel).molecule as! StringAndMoleculeModel).molecule) } try container.encodeModels(models, forKey: .list) + try container.encode(numberColor, forKey: .numberColor) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift index 3adb0bb3..6556796b 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift @@ -13,10 +13,12 @@ public class StringAndMoleculeModel: MoleculeModelProtocol { public var backgroundColor: Color? public var string: String public var molecule: MoleculeModelProtocol - - public init(string: String, molecule: MoleculeModelProtocol) { + public var stringColor: Color + + public init(string: String, molecule: MoleculeModelProtocol, stringColor: Color = Color(uiColor: .mvmBlack)) { self.string = string self.molecule = molecule + self.stringColor = stringColor } private enum CodingKeys: String, CodingKey { @@ -24,6 +26,7 @@ public class StringAndMoleculeModel: MoleculeModelProtocol { case backgroundColor case string case molecule + case stringColor } public required init(from decoder: Decoder) throws { @@ -31,6 +34,7 @@ public class StringAndMoleculeModel: MoleculeModelProtocol { backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) string = try typeContainer.decode(String.self, forKey: .string) molecule = try typeContainer.decodeModel(codingKey: .molecule) + stringColor = try typeContainer.decodeIfPresent(Color.self, forKey: .stringColor) ?? Color(uiColor: .mvmBlack) } public func encode(to encoder: Encoder) throws { @@ -39,5 +43,6 @@ public class StringAndMoleculeModel: MoleculeModelProtocol { try container.encode(string, forKey: .string) try container.encodeModel(molecule, forKey: .molecule) try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(stringColor, forKey: .stringColor) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift index a2a76f68..a9fd5da8 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift @@ -84,6 +84,7 @@ open class StringAndMoleculeView: View { super.set(with: model, delegateObject, additionalData) guard let model = model as? StringAndMoleculeModel else { return } label.text = model.string + label.textColor = model.stringColor.uiColor molecule.set(with: model.molecule, delegateObject, additionalData) } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift index 3866e918..4013f3aa 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift @@ -13,12 +13,14 @@ import Foundation return "unOrderedList" } public var bulletChar = "•" - + public var bulletColor: Color + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor case list case bulletChar + case bulletColor } // Numbered list model comes in the from of list = [MoleculeModelProtocol] @@ -28,10 +30,11 @@ import Foundation self.bulletChar = bulletChar } + bulletColor = try typeContainer.decodeIfPresent(Color.self, forKey: .bulletColor) ?? Color(uiColor: .mvmBlack) let list: [MoleculeModelProtocol] = try typeContainer.decodeModels(codingKey: .list) var models: [MoleculeStackItemModel] = [] for molecule in list { - models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule))) + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule, stringColor: bulletColor))) } super.init(molecules: models, spacing: 0) } @@ -47,5 +50,6 @@ import Foundation models.append(((molecule as! MoleculeStackItemModel).molecule as! StringAndMoleculeModel).molecule) } try container.encodeModels(models, forKey: .list) + try container.encode(bulletColor, forKey: .bulletColor) } } diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index 1229a146..c0dd6792 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -28,13 +28,13 @@ open class Styler { // Legacy Fonts case H1 - case H2 - case H3 case H32 + case H2 + case B20 + case H3 case B1 case B2 case B3 - case B20 /// Returns the font size of the current enum case. public func pointSize() -> CGFloat { @@ -45,8 +45,7 @@ open class Styler { case .Title2XLarge: return 36 - case .TitleXLarge, - .H32: + case .TitleXLarge, .H32: return 32 case .H2: @@ -57,8 +56,7 @@ open class Styler { return 24 case .BoldTitleMedium, - .RegularTitleMedium, - .B20: + .RegularTitleMedium, .B20: return 20 case .H3: @@ -68,15 +66,12 @@ open class Styler { .RegularBodyLarge: return 16 - case .BoldBodySmall, - .RegularBodySmall, - .B1, - .B2: + case .BoldBodySmall, .B1, + .RegularBodySmall, .B2: return 13 case .BoldMicro, - .RegularMicro, - .B3: + .RegularMicro, .B3: return 11 } } @@ -96,27 +91,22 @@ open class Styler { switch self { case .RegularTitleLarge, - .RegularTitleMedium, + .RegularTitleMedium, .B20, .RegularBodyLarge, - .RegularBodySmall, - .RegularMicro, - .B2, - .B3, - .B20: + .RegularBodySmall, .B2, + .RegularMicro, .B3: return false - case .Title2XLarge, - .TitleXLarge, + case .H1, + .Title2XLarge, + .TitleXLarge, .H32, + .H2, .BoldTitleLarge, .BoldTitleMedium, - .BoldBodyLarge, - .BoldBodySmall, - .BoldMicro, - .H1, - .H2, .H3, - .H32, - .B1: + .BoldBodyLarge, + .BoldBodySmall, .B1, + .BoldMicro: return true } } @@ -140,9 +130,9 @@ open class Styler { return false case .H1, + .H32, .H2, .H3, - .H32, .B1, .B2, .B3,