diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift index 65c4448b..22a18b94 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift @@ -74,7 +74,7 @@ extension BaseItemPickerEntryField { @objc open override func setAccessibilityString(_ accessibilityString: String?) { - var accessibilityString = accessibilityString ?? "" + let accessibilityString = accessibilityString ?? "" textField.accessibilityTraits = .staticText textField.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index a3780846..9d030d0f 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -397,7 +397,7 @@ extension TextEntryField { @objc open override func setAccessibilityString(_ accessibilityString: String?) { - var accessibilityString = accessibilityString ?? "" + let accessibilityString = accessibilityString ?? "" textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index 1bc399bb..7a836146 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -26,7 +26,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro /// Sets the current Index to focus on. public var currentIndex: Int = 0 public var animated: Bool = true - public var hidesForSinglePage: Bool = false + public var hidesForSinglePage: Bool = true public var inverted: Bool = false /// Set true to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is false public var accessibilityHasSlidesInsteadOfPage: Bool = false diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift index c60ac2ef..d500206c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift @@ -8,6 +8,7 @@ import Foundation import VDS +import MVMCore open class TileContainerModel: TileContainerBaseModel, ParentMoleculeModelProtocol, MoleculeModelProtocol { @@ -36,7 +37,7 @@ open class TileContainerModel: TileContainerBaseModel, Molec public var backgroundColor: Color? public var badge: Tilelet.BadgeModel? + public var eyebrow: LabelModel? + public var eyebrowColor: TitleLockup.TextColor = .primary public var title: LabelModel? + public var titleColor: TitleLockup.TitleTextColor = .primary public var subTitle: LabelModel? + public var subTitleColor: TitleLockup.TextColor = .primary public var descriptiveIcon: Tilelet.DescriptiveIcon? public var directionalIcon: Tilelet.DirectionalIcon? public var textWidth: CGFloat? @@ -30,8 +34,12 @@ open class TileletModel: TileContainerBaseModel, Molec case id case moleculeName case badge + case eyebrow + case eyebrowColor case title + case titleColor case subTitle + case subTitleColor case descriptiveIcon case directionalIcon case textWidth @@ -41,15 +49,64 @@ open class TileletModel: TileContainerBaseModel, Molec let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString badge = try container.decodeIfPresent(Tilelet.BadgeModel.self, forKey: .badge) + eyebrow = try container.decodeIfPresent(LabelModel.self, forKey: .eyebrow) title = try container.decodeIfPresent(LabelModel.self, forKey: .title) subTitle = try container.decodeIfPresent(LabelModel.self, forKey: .subTitle) descriptiveIcon = try container.decodeIfPresent(Tilelet.DescriptiveIcon.self, forKey: .descriptiveIcon) directionalIcon = try container.decodeIfPresent(Tilelet.DirectionalIcon.self, forKey: .directionalIcon) textWidth = try container.decodeIfPresent(CGFloat.self, forKey: .textWidth) textPercentage = try container.decodeIfPresent(CGFloat.self, forKey: .textPercentage) + + if let color = eyebrow?.textColor?.uiColor { + self.eyebrowColor = .custom(color, color) + + } else if let eyebrowColor = try? container.decodeIfPresent(TitleLockup.TextColor.self, forKey: .eyebrowColor) { + self.eyebrowColor = eyebrowColor + + } else { + eyebrowColor = .primary + } + + if let color = title?.textColor?.uiColor { + self.titleColor = .custom(color, color) + + } else if let titleColor = try? container.decodeIfPresent(TitleLockup.TitleTextColor.self, forKey: .titleColor) { + self.titleColor = titleColor + + } else { + titleColor = .primary + } + + if let color = subTitle?.textColor?.uiColor { + self.subTitleColor = .custom(color, color) + + } else if let subTitleColor = try? container.decodeIfPresent(TitleLockup.TextColor.self, forKey: .subTitleColor) { + self.subTitleColor = subTitleColor + + } else { + subTitleColor = .primary + } + try super.init(from: decoder) } + public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.EyebrowModel? { + guard let eyebrow else { return nil } + let attrs = eyebrow.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) + do { + if let style = eyebrow.fontStyle { + return .init(text: eyebrow.text, + textColor: eyebrowColor, + textAttributes: attrs, isBold: style.isBold(), + standardStyle: try style.vdsSubsetStyle()) + } + } catch MVMCoreError.errorObject(let object) { + MVMCoreLoggingHandler.shared()?.addError(toLog: object) + } catch { } + + return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs) + } + public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? { guard let title else { return nil } let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) @@ -57,6 +114,7 @@ open class TileletModel: TileContainerBaseModel, Molec do { if let style = title.fontStyle { return .init(text: title.text, + textColor: titleColor, textAttributes: attrs, standardStyle: try style.vdsSubsetStyle()) } @@ -65,7 +123,7 @@ open class TileletModel: TileContainerBaseModel, Molec MVMCoreLoggingHandler.shared()?.addError(toLog: object) } catch { } - return .init(text: title.text, textAttributes: attrs) + return .init(text: title.text, textColor: titleColor, textAttributes: attrs) } public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? { @@ -75,13 +133,14 @@ open class TileletModel: TileContainerBaseModel, Molec if let style = subTitle.fontStyle { return .init(text: subTitle.text, otherStandardStyle: try style.vdsSubsetStyle(), + textColor: subTitleColor, textAttributes: attrs) } } catch MVMCoreError.errorObject(let object) { MVMCoreLoggingHandler.shared()?.addError(toLog: object) } catch { } - return .init(text: subTitle.text, textAttributes: attrs) + return .init(text: subTitle.text, textColor: subTitleColor, textAttributes: attrs) } public override func encode(to encoder: Encoder) throws { @@ -89,8 +148,12 @@ open class TileletModel: TileContainerBaseModel, Molec try container.encode(id, forKey: .id) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(badge, forKey: .badge) - try container.encodeIfPresent(title, forKey: .title) - try container.encodeIfPresent(subTitle, forKey: .subTitle) + try container.encodeModelIfPresent(eyebrow, forKey: .eyebrow) + try container.encode(eyebrowColor, forKey: .eyebrowColor) + try container.encodeModelIfPresent(title, forKey: .title) + try container.encode(titleColor, forKey: .titleColor) + try container.encodeModelIfPresent(subTitle, forKey: .subTitle) + try container.encode(subTitleColor, forKey: .subTitleColor) try container.encodeIfPresent(descriptiveIcon, forKey: .descriptiveIcon) try container.encodeIfPresent(directionalIcon, forKey: .directionalIcon) try container.encodeIfPresent(textWidth, forKey: .textWidth) diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index f514248e..8bb75a80 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -75,7 +75,7 @@ extension VDS.TileContainerBase.BackgroundColor: Codable { var container = encoder.singleValueContainer() switch self { case .custom(let value): - try container.encode(value) + try container.encode(Color(uiColor: value)) default: try container.encode(String(reflecting: self)) } @@ -95,7 +95,11 @@ extension VDS.TileContainerBase.BackgroundColor: Codable { case "black": self = .black default: - self = .custom(type) + if let color = try? Color(from: decoder) { + self = .custom(color.uiColor) + } else { + self = .custom(UIColor(hexString: type)) + } } } } @@ -124,9 +128,9 @@ extension VDS.TileContainerBase.BackgroundEffect: Codable { case .none: self = .none case .gradient: - let firstColor = try container.decode(String.self, forKey: .firstColor) - let secondColor = try container.decode(String.self, forKey: .secondColor) - self = .gradient(firstColor, secondColor) + let firstColor = try container.decode(Color.self, forKey: .firstColor) + let secondColor = try container.decode(Color.self, forKey: .secondColor) + self = .gradient(firstColor.uiColor, secondColor.uiColor) } } @@ -139,8 +143,8 @@ extension VDS.TileContainerBase.BackgroundEffect: Codable { try container.encode(BackgroundEffectType.none.rawValue, forKey: .type) case .gradient(let firstColor, let secondColor): try container.encode(BackgroundEffectType.gradient.rawValue, forKey: .type) - try container.encode(firstColor, forKey: .firstColor) - try container.encode(secondColor, forKey: .secondColor) + try container.encode(Color(uiColor: firstColor), forKey: .firstColor) + try container.encode(Color(uiColor: secondColor), forKey: .secondColor) @unknown default: break } @@ -188,3 +192,92 @@ extension VDS.TileContainer.Padding: Codable { } } } + +extension VDS.TitleLockup.TextColor: Codable { + + enum CodingKeys: String, CodingKey { + case type + case lightColor + case darkColor + } + + enum CustomColorType: String, Codable { + case primary + case secondary + case custom + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(CustomColorType.self, forKey: .type) + + switch type { + case .primary: + self = .primary + case .secondary: + self = .secondary + case .custom: + let lightColor = try container.decode(Color.self, forKey: .lightColor) + let darkColor = try container.decode(Color.self, forKey: .darkColor) + self = .custom(lightColor.uiColor, darkColor.uiColor) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .primary: + try container.encode(CustomColorType.primary.rawValue, forKey: .type) + case .secondary: + try container.encode(CustomColorType.secondary.rawValue, forKey: .type) + case .custom(let lightColor, let darkColor): + try container.encode(CustomColorType.custom.rawValue, forKey: .type) + try container.encode(Color(uiColor: lightColor), forKey: .lightColor) + try container.encode(Color(uiColor: darkColor), forKey: .darkColor) + @unknown default: + break + } + } +} + +extension VDS.TitleLockup.TitleTextColor: Codable { + + enum CodingKeys: String, CodingKey { + case type + case lightColor + case darkColor + } + + enum CustomColorType: String, Codable { + case primary + case custom + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(CustomColorType.self, forKey: .type) + + switch type { + case .primary: + self = .primary + case .custom: + let lightColor = try container.decode(Color.self, forKey: .lightColor) + let darkColor = try container.decode(Color.self, forKey: .darkColor) + self = .custom(lightColor.uiColor, darkColor.uiColor) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .primary: + try container.encode(CustomColorType.primary.rawValue, forKey: .type) + case .custom(let lightColor, let darkColor): + try container.encode(CustomColorType.custom.rawValue, forKey: .type) + try container.encode(Color(uiColor: lightColor), forKey: .lightColor) + try container.encode(Color(uiColor: darkColor), forKey: .darkColor) + @unknown default: + break + } + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift index 9fec7e5b..05457cdf 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift @@ -91,25 +91,37 @@ public struct DeprecatedHeadlineBodyHelper { public func createTitleLockupModel(defaultStyle: HeadlineBodyModel.Style = .header, headlineBody: HeadlineBodyModel) throws -> TitleLockupModel { guard let headline = headlineBody.headline else { throw ModelRegistry.Error.decoderOther(message: "headline is required for this use case.") } - var body = headlineBody.body + let body = headlineBody.body + var defaultHeadlineStyle: Styler.Font + var defaultBodyStyle: Styler.Font switch headlineBody.style ?? defaultStyle { case .landingHeader: - headline.fontStyle = Styler.Font.RegularTitle2XLarge - body?.fontStyle = Styler.Font.RegularTitleMedium + defaultHeadlineStyle = Styler.Font.RegularTitle2XLarge + defaultBodyStyle = Styler.Font.RegularTitleMedium case .itemHeader: - headline.fontStyle = Styler.Font.BoldTitleLarge - body?.fontStyle = Styler.Font.RegularBodyLarge + defaultHeadlineStyle = Styler.Font.BoldTitleLarge + defaultBodyStyle = Styler.Font.RegularBodyLarge default: - headline.fontStyle = Styler.Font.RegularTitleXLarge - body?.fontStyle = Styler.Font.RegularTitleMedium + defaultHeadlineStyle = Styler.Font.RegularTitleXLarge + defaultBodyStyle = Styler.Font.RegularTitleMedium } - let model = try TitleLockupModel(title: headline, subTitle: body) + if headline.fontStyle == nil { + headline.fontStyle = defaultHeadlineStyle + } + if body?.fontStyle == nil { + body?.fontStyle = defaultBodyStyle + } + let model = TitleLockupModel(title: headline, subTitle: body) model.id = headlineBody.id + if let textAlignment = headline.textAlignment ?? body?.textAlignment, + textAlignment == .center { + model.textAlignment = .center + } return model } public func createHeadlineBodyModel(titleLockup: TitleLockupModel) -> HeadlineBodyModel { - var headlineBody = HeadlineBodyModel(headline: titleLockup.title) + let headlineBody = HeadlineBodyModel(headline: titleLockup.title) headlineBody.body = titleLockup.subTitle headlineBody.id = titleLockup.id return headlineBody diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift index a9ae8602..582b3095 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -21,9 +21,11 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { public var textAlignment: TitleLockup.TextAlignment = .left public var eyebrow: LabelModel? + public var eyebrowColor: TitleLockup.TextColor = .primary public var title: LabelModel + public var titleColor: TitleLockup.TitleTextColor = .primary public var subTitle: LabelModel? - public var subTitleColor: Use = .primary + public var subTitleColor: TitleLockup.TextColor = .primary public var alignment: VDS.TitleLockup.TextAlignment = .left public var inverted: Bool = false @@ -73,7 +75,9 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { case moleculeName case textAlignment case eyebrow + case eyebrowColor case title + case titleColor case subTitle case subTitleColor case inverted @@ -92,17 +96,36 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) - /// look for color hex code - if let color = try? typeContainer.decodeIfPresent(Color.self, forKey: .subTitleColor) { - self.subTitleColor = color.uiColor.isDark() ? .primary : .secondary + if let color = eyebrow?.textColor?.uiColor { + self.eyebrowColor = .custom(color, color) - } else if let subTitleColor = try? typeContainer.decodeIfPresent(Use.self, forKey: .subTitleColor) { + } else if let eyebrowColor = try? typeContainer.decodeIfPresent(TitleLockup.TextColor.self, forKey: .eyebrowColor) { + self.eyebrowColor = eyebrowColor + + } else { + eyebrowColor = .primary + } + + if let color = title.textColor?.uiColor { + self.titleColor = .custom(color, color) + + } else if let titleColor = try? typeContainer.decodeIfPresent(TitleLockup.TitleTextColor.self, forKey: .titleColor) { + self.titleColor = titleColor + + } else { + titleColor = .primary + } + + if let color = subTitle?.textColor?.uiColor { + self.subTitleColor = .custom(color, color) + + } else if let subTitleColor = try? typeContainer.decodeIfPresent(TitleLockup.TextColor.self, forKey: .subTitleColor) { self.subTitleColor = subTitleColor } else { subTitleColor = .primary } - + if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) { alignment = newAlignment } @@ -121,7 +144,9 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { try container.encode(moleculeName, forKey: .moleculeName) try container.encode(textAlignment, forKey: .textAlignment) try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encode(eyebrowColor, forKey: .eyebrowColor) try container.encodeModel(title, forKey: .title) + try container.encode(titleColor, forKey: .titleColor) try container.encodeIfPresent(subTitle, forKey: .subTitle) try container.encode(subTitleColor, forKey: .subTitleColor) try container.encode(alignment, forKey: .alignment) @@ -134,6 +159,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { do { if let style = eyebrow.fontStyle { return .init(text: eyebrow.text, + textColor: eyebrowColor, isBold: style.isBold(), standardStyle: try style.vdsSubsetStyle(), textAttributes: attrs, @@ -143,7 +169,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { MVMCoreLoggingHandler.shared()?.addError(toLog: object) } catch { } - return .init(text: eyebrow.text, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0) + return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0) } public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.TitleModel { @@ -151,6 +177,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { do { if let style = title.fontStyle { return .init(text: title.text, + textColor: titleColor, textAttributes: attrs, isBold: style.isBold(), standardStyle: try style.vdsSubsetStyle(), @@ -161,7 +188,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { MVMCoreLoggingHandler.shared()?.addError(toLog: object) } catch { } - return .init(text: title.text, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0) + return .init(text: title.text, textColor: titleColor, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0) } public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.SubTitleModel? { diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift index 301a2274..509c9e61 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift @@ -22,7 +22,7 @@ open class CarouselItem: MoleculeCollectionViewCell, CarouselItemProtocol { open override func setupView() { super.setupView() - clipsToBounds = true + clipsToBounds = false // Covers the card when peaking. peakingCover.backgroundColor = .white diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index b8e62030..a062677b 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -106,7 +106,7 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol { public extension HeadlineBodyModel { func createHeaderTitleLockupModel(defaultStyle: Style = .header) throws -> TitleLockupModel { guard let headline = headline else { throw ModelRegistry.Error.decoderOther(message: "headline is required for this use case.") } - var body = self.body + let body = self.body switch style ?? defaultStyle { case .landingHeader: headline.fontStyle = Styler.Font.RegularTitle2XLarge @@ -118,7 +118,7 @@ public extension HeadlineBodyModel { headline.fontStyle = Styler.Font.RegularTitleXLarge body?.fontStyle = Styler.Font.RegularTitleMedium } - let model = try TitleLockupModel(title: headline, subTitle: body) + let model = TitleLockupModel(title: headline, subTitle: body) model.id = id return model } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index c4f7d825..914e6427 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -69,7 +69,7 @@ open class Carousel: View { public var delegateObject: MVMCoreUIDelegateObject? private var size: CGFloat? - + // Updates the model and index. public func updateModelIndex() { (model as? CarouselModel)?.index = pageIndex @@ -90,12 +90,29 @@ open class Carousel: View { showPeaking(false) // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. - guard let model = model as? CarouselModel, !model.molecules.isEmpty, - (model.paging == true || loop == true) else { return } + guard let model = model as? CarouselModel, !model.molecules.isEmpty else { return } + guard (model.paging == true || loop == true) else { + DispatchQueue.main.async { [self] in + updatePagerVisibility() + } + return + } DispatchQueue.main.async { [self] in collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false) collectionView.layoutIfNeeded() showPeaking(true) + updatePagerVisibility() + } + } + + /// Updates if the pager is visible or not. + private func updatePagerVisibility() { + guard let pagingView = pagingView else { return } + let shouldHidePager = collectionView.contentSize.width < bounds.width + if (shouldHidePager && !pagingView.isHidden) || (!shouldHidePager && pagingView.isHidden) { + pagingView.isHidden = shouldHidePager + pagingBottomPin?.isActive = !shouldHidePager + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } } @@ -108,6 +125,7 @@ open class Carousel: View { collectionView.dataSource = self collectionView.delegate = self collectionView.bounces = false + collectionView.clipsToBounds = false addSubview(collectionView) bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) @@ -140,16 +158,6 @@ open class Carousel: View { (cell as? MVMCoreViewProtocol)?.updateView(size) } layoutCollection() - - // Check must be dispatched to main for the layout to complete in layoutCollection. - DispatchQueue.main.async { [self] in - let shouldHidePager = molecules?.count ?? 0 < 2 || collectionView.contentSize.width < bounds.width - if let pagingView = pagingView, shouldHidePager != pagingView.isHidden { - pagingView.isHidden = shouldHidePager - pagingBottomPin?.isActive = !shouldHidePager - delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) - } - } } //-------------------------------------------------- @@ -244,7 +252,7 @@ open class Carousel: View { var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)? = nil if let molecule = molecule, - (!molecule.hidesForSinglePage || numberOfPages > 1) { + (numberOfPages > 1 || !molecule.hidesForSinglePage) { pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol) pagingMoleculeName = molecule.moleculeName } else { diff --git a/MVMCoreUI/Behaviors/GetContactBehavior.swift b/MVMCoreUI/Behaviors/GetContactBehavior.swift index 6f9a3cd4..755c5706 100644 --- a/MVMCoreUI/Behaviors/GetContactBehavior.swift +++ b/MVMCoreUI/Behaviors/GetContactBehavior.swift @@ -35,16 +35,16 @@ public class PageGetContactBehavior: PageVisibilityBehavior { CNContactStore().requestAccess(for: .contacts) { [weak self] (access, error) in guard access, error == nil, - let rootMolecules = self?.delegate?.moleculeDelegate?.getRootMolecules() else { return } + let rootMolecules = delegateObject?.moleculeDelegate?.getRootMolecules() else { return } // Iterate models and provide contact self?.getContacts(for: rootMolecules) // Tell template to update - MVMCoreDispatchUtility.performBlock(onMainThread: { + Task { @MainActor in // TODO: move to protocol function instead - guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return } + guard let controller = delegateObject?.moleculeDelegate as? ViewController else { return } controller.handleNewData() - }) + } } } diff --git a/MVMCoreUI/Categories/UIColor+Extension.swift b/MVMCoreUI/Categories/UIColor+Extension.swift index f4ef0f7f..1d2158a6 100644 --- a/MVMCoreUI/Categories/UIColor+Extension.swift +++ b/MVMCoreUI/Categories/UIColor+Extension.swift @@ -230,6 +230,12 @@ extension UIColor { return UIColor(named: name, in: MVMCoreUIUtility.bundleForMVMCoreUI(), compatibleWith: nil)! } + /// Returns a color corresponding to the passed in color name. + @objc + public static func mvmCoreUIColor(with name: String) -> UIColor? { + return UIColor.names[name]?.uiColor + } + /// Convenience to get a grayscale UIColor where the same value is used for red, green, and blue. public class func grayscale(rgb: Int, alpha: CGFloat = 1.0) -> UIColor { diff --git a/MVMCoreUI/Categories/UIColor+MFConvenience.m b/MVMCoreUI/Categories/UIColor+MFConvenience.m index cd809509..f33b71b0 100644 --- a/MVMCoreUI/Categories/UIColor+MFConvenience.m +++ b/MVMCoreUI/Categories/UIColor+MFConvenience.m @@ -7,6 +7,7 @@ // #import "UIColor+MFConvenience.h" +#import @import MVMCore.MVMCoreDispatchUtility; @implementation UIColor (MFConvenience) @@ -298,6 +299,10 @@ } + (nullable UIColor *)mfGetColorForString:(nullable NSString *)string { + if ([string hasPrefix:@"#"]) { + return [self mfGetColorForHex:string]; + } + static NSDictionary *stringColorMapping; static dispatch_once_t once; dispatch_once(&once, ^{ @@ -327,14 +332,9 @@ UIColor *color = nil; if (string && string.length > 0) { - color = [stringColorMapping objectForKey:string]; - if (!color){ - color = [UIColor blackColor]; - } - } else { - color = [UIColor blackColor]; + color = [stringColorMapping objectForKey:string] ?: [UIColor mvmCoreUIColorWith:string]; } - return color; + return color ?: [UIColor blackColor]; } + (nonnull UIColor *)mfGetColorForHex:(nonnull NSString *) hexString { diff --git a/MVMCoreUI/Categories/UIStackView+Extension.swift b/MVMCoreUI/Categories/UIStackView+Extension.swift index b6bc1b05..e6ed0dda 100644 --- a/MVMCoreUI/Categories/UIStackView+Extension.swift +++ b/MVMCoreUI/Categories/UIStackView+Extension.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore extension UIStackView: MVMCoreViewProtocol { public func updateView(_ size: CGFloat) { @@ -16,7 +17,7 @@ extension UIStackView: MVMCoreViewProtocol { } /// A convenience function for updating molecules. If model is nil, view is hidden. - open func updateContainedMolecules(with models: [MoleculeModelProtocol?], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + public func updateContainedMolecules(with models: [MoleculeModelProtocol?], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { for (index, item) in arrangedSubviews.enumerated() { if let model = models[index] { (item as? MoleculeViewProtocol)?.set(with: model, delegateObject, additionalData) diff --git a/MVMCoreUI/CustomPrimitives/Color.swift b/MVMCoreUI/CustomPrimitives/Color.swift index a67f8747..475e8aea 100644 --- a/MVMCoreUI/CustomPrimitives/Color.swift +++ b/MVMCoreUI/CustomPrimitives/Color.swift @@ -87,11 +87,14 @@ public final class Color: Codable { let colorString = try container.decode(String.self) if let vdsColor = UIColor.VDSColor(rawValue: colorString) { - self.uiColor = vdsColor.uiColor + uiColor = vdsColor.uiColor hex = uiColor.hexString ?? "" + } else if let color = Color(name: colorString) { + uiColor = color.uiColor + hex = color.hex } else { let components = try Color.getColorComponents(for: colorString) - self.uiColor = components.color + uiColor = components.color hex = components.hex name = components.name ?? "" } diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index 2c2e4ae7..2bde433a 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -404,7 +404,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, public func update(percentage: CGFloat) { guard customInteractor?.interactive == true, - let index = index else { return } + let _ = index else { return } // tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage) } }