Merge remote-tracking branch 'refs/remotes/origin/develop'

This commit is contained in:
Hedden, Kyle Matthew 2024-05-08 10:12:25 -04:00
commit 658850400f
18 changed files with 279 additions and 64 deletions

View File

@ -74,7 +74,7 @@ extension BaseItemPickerEntryField {
@objc open override func setAccessibilityString(_ accessibilityString: String?) { @objc open override func setAccessibilityString(_ accessibilityString: String?) {
var accessibilityString = accessibilityString ?? "" let accessibilityString = accessibilityString ?? ""
textField.accessibilityTraits = .staticText textField.accessibilityTraits = .staticText
textField.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") textField.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"

View File

@ -397,7 +397,7 @@ extension TextEntryField {
@objc open override func setAccessibilityString(_ accessibilityString: String?) { @objc open override func setAccessibilityString(_ accessibilityString: String?) {
var accessibilityString = accessibilityString ?? "" let accessibilityString = accessibilityString ?? ""
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
} }

View File

@ -26,7 +26,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
/// Sets the current Index to focus on. /// Sets the current Index to focus on.
public var currentIndex: Int = 0 public var currentIndex: Int = 0
public var animated: Bool = true public var animated: Bool = true
public var hidesForSinglePage: Bool = false public var hidesForSinglePage: Bool = true
public var inverted: Bool = false 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 /// 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 public var accessibilityHasSlidesInsteadOfPage: Bool = false

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
import VDS import VDS
import MVMCore
open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, TileContainer>, ParentMoleculeModelProtocol, MoleculeModelProtocol { open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, TileContainer>, ParentMoleculeModelProtocol, MoleculeModelProtocol {
@ -36,7 +37,7 @@ open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, Til
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
molecule = try container.decodeModelIfPresent(codingKey: .molecule) molecule = try container.decodeMoleculeIfPresent(codingKey: .molecule)
try super.init(from: decoder) try super.init(from: decoder)
} }

View File

@ -45,6 +45,7 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
} else if let percentage = viewModel.textPercentage { } else if let percentage = viewModel.textPercentage {
textWidth = .percentage(percentage) textWidth = .percentage(percentage)
} }
eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)
titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData) titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData)
subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData) subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData)
badgeModel = viewModel.badge badgeModel = viewModel.badge

View File

@ -19,8 +19,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
public var backgroundColor: Color? public var backgroundColor: Color?
public var badge: Tilelet.BadgeModel? public var badge: Tilelet.BadgeModel?
public var eyebrow: LabelModel?
public var eyebrowColor: TitleLockup.TextColor = .primary
public var title: LabelModel? public var title: LabelModel?
public var titleColor: TitleLockup.TitleTextColor = .primary
public var subTitle: LabelModel? public var subTitle: LabelModel?
public var subTitleColor: TitleLockup.TextColor = .primary
public var descriptiveIcon: Tilelet.DescriptiveIcon? public var descriptiveIcon: Tilelet.DescriptiveIcon?
public var directionalIcon: Tilelet.DirectionalIcon? public var directionalIcon: Tilelet.DirectionalIcon?
public var textWidth: CGFloat? public var textWidth: CGFloat?
@ -30,8 +34,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
case id case id
case moleculeName case moleculeName
case badge case badge
case eyebrow
case eyebrowColor
case title case title
case titleColor
case subTitle case subTitle
case subTitleColor
case descriptiveIcon case descriptiveIcon
case directionalIcon case directionalIcon
case textWidth case textWidth
@ -41,15 +49,64 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
badge = try container.decodeIfPresent(Tilelet.BadgeModel.self, forKey: .badge) 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) title = try container.decodeIfPresent(LabelModel.self, forKey: .title)
subTitle = try container.decodeIfPresent(LabelModel.self, forKey: .subTitle) subTitle = try container.decodeIfPresent(LabelModel.self, forKey: .subTitle)
descriptiveIcon = try container.decodeIfPresent(Tilelet.DescriptiveIcon.self, forKey: .descriptiveIcon) descriptiveIcon = try container.decodeIfPresent(Tilelet.DescriptiveIcon.self, forKey: .descriptiveIcon)
directionalIcon = try container.decodeIfPresent(Tilelet.DirectionalIcon.self, forKey: .directionalIcon) directionalIcon = try container.decodeIfPresent(Tilelet.DirectionalIcon.self, forKey: .directionalIcon)
textWidth = try container.decodeIfPresent(CGFloat.self, forKey: .textWidth) textWidth = try container.decodeIfPresent(CGFloat.self, forKey: .textWidth)
textPercentage = try container.decodeIfPresent(CGFloat.self, forKey: .textPercentage) 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) 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? { public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? {
guard let title else { return nil } guard let title else { return nil }
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
@ -57,6 +114,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
do { do {
if let style = title.fontStyle { if let style = title.fontStyle {
return .init(text: title.text, return .init(text: title.text,
textColor: titleColor,
textAttributes: attrs, textAttributes: attrs,
standardStyle: try style.vdsSubsetStyle()) standardStyle: try style.vdsSubsetStyle())
} }
@ -65,7 +123,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
MVMCoreLoggingHandler.shared()?.addError(toLog: object) MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { } } 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? { public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {
@ -75,13 +133,14 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
if let style = subTitle.fontStyle { if let style = subTitle.fontStyle {
return .init(text: subTitle.text, return .init(text: subTitle.text,
otherStandardStyle: try style.vdsSubsetStyle(), otherStandardStyle: try style.vdsSubsetStyle(),
textColor: subTitleColor,
textAttributes: attrs) textAttributes: attrs)
} }
} catch MVMCoreError.errorObject(let object) { } catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object) MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { } } 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 { public override func encode(to encoder: Encoder) throws {
@ -89,8 +148,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(badge, forKey: .badge) try container.encodeIfPresent(badge, forKey: .badge)
try container.encodeIfPresent(title, forKey: .title) try container.encodeModelIfPresent(eyebrow, forKey: .eyebrow)
try container.encodeIfPresent(subTitle, forKey: .subTitle) 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(descriptiveIcon, forKey: .descriptiveIcon)
try container.encodeIfPresent(directionalIcon, forKey: .directionalIcon) try container.encodeIfPresent(directionalIcon, forKey: .directionalIcon)
try container.encodeIfPresent(textWidth, forKey: .textWidth) try container.encodeIfPresent(textWidth, forKey: .textWidth)

View File

@ -75,7 +75,7 @@ extension VDS.TileContainerBase.BackgroundColor: Codable {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
switch self { switch self {
case .custom(let value): case .custom(let value):
try container.encode(value) try container.encode(Color(uiColor: value))
default: default:
try container.encode(String(reflecting: self)) try container.encode(String(reflecting: self))
} }
@ -95,7 +95,11 @@ extension VDS.TileContainerBase.BackgroundColor: Codable {
case "black": case "black":
self = .black self = .black
default: 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: case .none:
self = .none self = .none
case .gradient: case .gradient:
let firstColor = try container.decode(String.self, forKey: .firstColor) let firstColor = try container.decode(Color.self, forKey: .firstColor)
let secondColor = try container.decode(String.self, forKey: .secondColor) let secondColor = try container.decode(Color.self, forKey: .secondColor)
self = .gradient(firstColor, secondColor) self = .gradient(firstColor.uiColor, secondColor.uiColor)
} }
} }
@ -139,8 +143,8 @@ extension VDS.TileContainerBase.BackgroundEffect: Codable {
try container.encode(BackgroundEffectType.none.rawValue, forKey: .type) try container.encode(BackgroundEffectType.none.rawValue, forKey: .type)
case .gradient(let firstColor, let secondColor): case .gradient(let firstColor, let secondColor):
try container.encode(BackgroundEffectType.gradient.rawValue, forKey: .type) try container.encode(BackgroundEffectType.gradient.rawValue, forKey: .type)
try container.encode(firstColor, forKey: .firstColor) try container.encode(Color(uiColor: firstColor), forKey: .firstColor)
try container.encode(secondColor, forKey: .secondColor) try container.encode(Color(uiColor: secondColor), forKey: .secondColor)
@unknown default: @unknown default:
break 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
}
}
}

View File

@ -91,25 +91,37 @@ public struct DeprecatedHeadlineBodyHelper {
public func createTitleLockupModel(defaultStyle: HeadlineBodyModel.Style = .header, headlineBody: HeadlineBodyModel) throws -> TitleLockupModel { 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.") } 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 { switch headlineBody.style ?? defaultStyle {
case .landingHeader: case .landingHeader:
headline.fontStyle = Styler.Font.RegularTitle2XLarge defaultHeadlineStyle = Styler.Font.RegularTitle2XLarge
body?.fontStyle = Styler.Font.RegularTitleMedium defaultBodyStyle = Styler.Font.RegularTitleMedium
case .itemHeader: case .itemHeader:
headline.fontStyle = Styler.Font.BoldTitleLarge defaultHeadlineStyle = Styler.Font.BoldTitleLarge
body?.fontStyle = Styler.Font.RegularBodyLarge defaultBodyStyle = Styler.Font.RegularBodyLarge
default: default:
headline.fontStyle = Styler.Font.RegularTitleXLarge defaultHeadlineStyle = Styler.Font.RegularTitleXLarge
body?.fontStyle = Styler.Font.RegularTitleMedium 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 model.id = headlineBody.id
if let textAlignment = headline.textAlignment ?? body?.textAlignment,
textAlignment == .center {
model.textAlignment = .center
}
return model return model
} }
public func createHeadlineBodyModel(titleLockup: TitleLockupModel) -> HeadlineBodyModel { public func createHeadlineBodyModel(titleLockup: TitleLockupModel) -> HeadlineBodyModel {
var headlineBody = HeadlineBodyModel(headline: titleLockup.title) let headlineBody = HeadlineBodyModel(headline: titleLockup.title)
headlineBody.body = titleLockup.subTitle headlineBody.body = titleLockup.subTitle
headlineBody.id = titleLockup.id headlineBody.id = titleLockup.id
return headlineBody return headlineBody

View File

@ -21,9 +21,11 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
public var textAlignment: TitleLockup.TextAlignment = .left public var textAlignment: TitleLockup.TextAlignment = .left
public var eyebrow: LabelModel? public var eyebrow: LabelModel?
public var eyebrowColor: TitleLockup.TextColor = .primary
public var title: LabelModel public var title: LabelModel
public var titleColor: TitleLockup.TitleTextColor = .primary
public var subTitle: LabelModel? public var subTitle: LabelModel?
public var subTitleColor: Use = .primary public var subTitleColor: TitleLockup.TextColor = .primary
public var alignment: VDS.TitleLockup.TextAlignment = .left public var alignment: VDS.TitleLockup.TextAlignment = .left
public var inverted: Bool = false public var inverted: Bool = false
@ -73,7 +75,9 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
case moleculeName case moleculeName
case textAlignment case textAlignment
case eyebrow case eyebrow
case eyebrowColor
case title case title
case titleColor
case subTitle case subTitle
case subTitleColor case subTitleColor
case inverted case inverted
@ -92,17 +96,36 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle)
/// look for color hex code if let color = eyebrow?.textColor?.uiColor {
if let color = try? typeContainer.decodeIfPresent(Color.self, forKey: .subTitleColor) { self.eyebrowColor = .custom(color, color)
self.subTitleColor = color.uiColor.isDark() ? .primary : .secondary
} 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 self.subTitleColor = subTitleColor
} else { } else {
subTitleColor = .primary subTitleColor = .primary
} }
if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) { if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) {
alignment = newAlignment alignment = newAlignment
} }
@ -121,7 +144,9 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(textAlignment, forKey: .textAlignment) try container.encode(textAlignment, forKey: .textAlignment)
try container.encodeIfPresent(eyebrow, forKey: .eyebrow) try container.encodeIfPresent(eyebrow, forKey: .eyebrow)
try container.encode(eyebrowColor, forKey: .eyebrowColor)
try container.encodeModel(title, forKey: .title) try container.encodeModel(title, forKey: .title)
try container.encode(titleColor, forKey: .titleColor)
try container.encodeIfPresent(subTitle, forKey: .subTitle) try container.encodeIfPresent(subTitle, forKey: .subTitle)
try container.encode(subTitleColor, forKey: .subTitleColor) try container.encode(subTitleColor, forKey: .subTitleColor)
try container.encode(alignment, forKey: .alignment) try container.encode(alignment, forKey: .alignment)
@ -134,6 +159,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
do { do {
if let style = eyebrow.fontStyle { if let style = eyebrow.fontStyle {
return .init(text: eyebrow.text, return .init(text: eyebrow.text,
textColor: eyebrowColor,
isBold: style.isBold(), isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(), standardStyle: try style.vdsSubsetStyle(),
textAttributes: attrs, textAttributes: attrs,
@ -143,7 +169,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
MVMCoreLoggingHandler.shared()?.addError(toLog: object) MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { } } 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 { public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.TitleModel {
@ -151,6 +177,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
do { do {
if let style = title.fontStyle { if let style = title.fontStyle {
return .init(text: title.text, return .init(text: title.text,
textColor: titleColor,
textAttributes: attrs, textAttributes: attrs,
isBold: style.isBold(), isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(), standardStyle: try style.vdsSubsetStyle(),
@ -161,7 +188,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
MVMCoreLoggingHandler.shared()?.addError(toLog: object) MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { } } 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? { public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.SubTitleModel? {

View File

@ -22,7 +22,7 @@ open class CarouselItem: MoleculeCollectionViewCell, CarouselItemProtocol {
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
clipsToBounds = true clipsToBounds = false
// Covers the card when peaking. // Covers the card when peaking.
peakingCover.backgroundColor = .white peakingCover.backgroundColor = .white

View File

@ -106,7 +106,7 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol {
public extension HeadlineBodyModel { public extension HeadlineBodyModel {
func createHeaderTitleLockupModel(defaultStyle: Style = .header) throws -> TitleLockupModel { func createHeaderTitleLockupModel(defaultStyle: Style = .header) throws -> TitleLockupModel {
guard let headline = headline else { throw ModelRegistry.Error.decoderOther(message: "headline is required for this use case.") } 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 { switch style ?? defaultStyle {
case .landingHeader: case .landingHeader:
headline.fontStyle = Styler.Font.RegularTitle2XLarge headline.fontStyle = Styler.Font.RegularTitle2XLarge
@ -118,7 +118,7 @@ public extension HeadlineBodyModel {
headline.fontStyle = Styler.Font.RegularTitleXLarge headline.fontStyle = Styler.Font.RegularTitleXLarge
body?.fontStyle = Styler.Font.RegularTitleMedium body?.fontStyle = Styler.Font.RegularTitleMedium
} }
let model = try TitleLockupModel(title: headline, subTitle: body) let model = TitleLockupModel(title: headline, subTitle: body)
model.id = id model.id = id
return model return model
} }

View File

@ -69,7 +69,7 @@ open class Carousel: View {
public var delegateObject: MVMCoreUIDelegateObject? public var delegateObject: MVMCoreUIDelegateObject?
private var size: CGFloat? private var size: CGFloat?
// Updates the model and index. // Updates the model and index.
public func updateModelIndex() { public func updateModelIndex() {
(model as? CarouselModel)?.index = pageIndex (model as? CarouselModel)?.index = pageIndex
@ -90,12 +90,29 @@ open class Carousel: View {
showPeaking(false) 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. // 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, guard let model = model as? CarouselModel, !model.molecules.isEmpty else { return }
(model.paging == true || loop == true) else { return } guard (model.paging == true || loop == true) else {
DispatchQueue.main.async { [self] in
updatePagerVisibility()
}
return
}
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false) collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false)
collectionView.layoutIfNeeded() collectionView.layoutIfNeeded()
showPeaking(true) 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.dataSource = self
collectionView.delegate = self collectionView.delegate = self
collectionView.bounces = false collectionView.bounces = false
collectionView.clipsToBounds = false
addSubview(collectionView) addSubview(collectionView)
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
@ -140,16 +158,6 @@ open class Carousel: View {
(cell as? MVMCoreViewProtocol)?.updateView(size) (cell as? MVMCoreViewProtocol)?.updateView(size)
} }
layoutCollection() 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 var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)? = nil
if let molecule = molecule, if let molecule = molecule,
(!molecule.hidesForSinglePage || numberOfPages > 1) { (numberOfPages > 1 || !molecule.hidesForSinglePage) {
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol) pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol)
pagingMoleculeName = molecule.moleculeName pagingMoleculeName = molecule.moleculeName
} else { } else {

View File

@ -35,16 +35,16 @@ public class PageGetContactBehavior: PageVisibilityBehavior {
CNContactStore().requestAccess(for: .contacts) { [weak self] (access, error) in CNContactStore().requestAccess(for: .contacts) { [weak self] (access, error) in
guard access, guard access,
error == nil, error == nil,
let rootMolecules = self?.delegate?.moleculeDelegate?.getRootMolecules() else { return } let rootMolecules = delegateObject?.moleculeDelegate?.getRootMolecules() else { return }
// Iterate models and provide contact // Iterate models and provide contact
self?.getContacts(for: rootMolecules) self?.getContacts(for: rootMolecules)
// Tell template to update // Tell template to update
MVMCoreDispatchUtility.performBlock(onMainThread: { Task { @MainActor in
// TODO: move to protocol function instead // 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() controller.handleNewData()
}) }
} }
} }

View File

@ -230,6 +230,12 @@ extension UIColor {
return UIColor(named: name, in: MVMCoreUIUtility.bundleForMVMCoreUI(), compatibleWith: nil)! 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. /// 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 { public class func grayscale(rgb: Int, alpha: CGFloat = 1.0) -> UIColor {

View File

@ -7,6 +7,7 @@
// //
#import "UIColor+MFConvenience.h" #import "UIColor+MFConvenience.h"
#import <MVMCoreUI/MVMCoreUI-Swift.h>
@import MVMCore.MVMCoreDispatchUtility; @import MVMCore.MVMCoreDispatchUtility;
@implementation UIColor (MFConvenience) @implementation UIColor (MFConvenience)
@ -298,6 +299,10 @@
} }
+ (nullable UIColor *)mfGetColorForString:(nullable NSString *)string { + (nullable UIColor *)mfGetColorForString:(nullable NSString *)string {
if ([string hasPrefix:@"#"]) {
return [self mfGetColorForHex:string];
}
static NSDictionary *stringColorMapping; static NSDictionary *stringColorMapping;
static dispatch_once_t once; static dispatch_once_t once;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
@ -327,14 +332,9 @@
UIColor *color = nil; UIColor *color = nil;
if (string && string.length > 0) { if (string && string.length > 0) {
color = [stringColorMapping objectForKey:string]; color = [stringColorMapping objectForKey:string] ?: [UIColor mvmCoreUIColorWith:string];
if (!color){
color = [UIColor blackColor];
}
} else {
color = [UIColor blackColor];
} }
return color; return color ?: [UIColor blackColor];
} }
+ (nonnull UIColor *)mfGetColorForHex:(nonnull NSString *) hexString { + (nonnull UIColor *)mfGetColorForHex:(nonnull NSString *) hexString {

View File

@ -7,6 +7,7 @@
// //
import Foundation import Foundation
import MVMCore
extension UIStackView: MVMCoreViewProtocol { extension UIStackView: MVMCoreViewProtocol {
public func updateView(_ size: CGFloat) { 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. /// 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() { for (index, item) in arrangedSubviews.enumerated() {
if let model = models[index] { if let model = models[index] {
(item as? MoleculeViewProtocol)?.set(with: model, delegateObject, additionalData) (item as? MoleculeViewProtocol)?.set(with: model, delegateObject, additionalData)

View File

@ -87,11 +87,14 @@ public final class Color: Codable {
let colorString = try container.decode(String.self) let colorString = try container.decode(String.self)
if let vdsColor = UIColor.VDSColor(rawValue: colorString) { if let vdsColor = UIColor.VDSColor(rawValue: colorString) {
self.uiColor = vdsColor.uiColor uiColor = vdsColor.uiColor
hex = uiColor.hexString ?? "" hex = uiColor.hexString ?? ""
} else if let color = Color(name: colorString) {
uiColor = color.uiColor
hex = color.hex
} else { } else {
let components = try Color.getColorComponents(for: colorString) let components = try Color.getColorComponents(for: colorString)
self.uiColor = components.color uiColor = components.color
hex = components.hex hex = components.hex
name = components.name ?? "" name = components.name ?? ""
} }

View File

@ -404,7 +404,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
public func update(percentage: CGFloat) { public func update(percentage: CGFloat) {
guard customInteractor?.interactive == true, guard customInteractor?.interactive == true,
let index = index else { return } let _ = index else { return }
// tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage) // tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
} }
} }