diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4e9fdca8..b7c94d3f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -578,6 +578,8 @@ EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; + EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642902BCDA97300D81DC4 /* TileContainer.swift */; }; + EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */; }; EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; }; EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */; }; EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D815F2B2B6E6800D29F9E /* Icon.swift */; }; @@ -1184,6 +1186,8 @@ EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; + EA6642902BCDA97300D81DC4 /* TileContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainer.swift; sourceTree = ""; }; + EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainerModel.swift; sourceTree = ""; }; EA6E8B942B504A43000139B4 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = ""; }; EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupModel.swift; sourceTree = ""; }; EA7D815F2B2B6E6800D29F9E /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = ""; }; @@ -2274,6 +2278,8 @@ EA1758472BC97ED800A5C0D9 /* BadgeIndicator.swift */, EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */, EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */, + EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */, + EA6642902BCDA97300D81DC4 /* TileContainer.swift */, EA985C3F2970939A00F2FF2E /* TileletModel.swift */, EA985C3D2970938F00F2FF2E /* Tilelet.swift */, EA7D81612B2B6E7F00D29F9E /* IconModel.swift */, @@ -3063,6 +3069,7 @@ 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, 0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */, D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */, + EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */, AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */, 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */, @@ -3189,6 +3196,7 @@ D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */, 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */, D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, + EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */, 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, 27F6B08C26052AFF008529AA /* ParentMoleculeModelProtocol.swift in Sources */, EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift new file mode 100644 index 00000000..3c588795 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift @@ -0,0 +1,89 @@ +// +// TileContainer.swift +// MVMCoreUI +// +// Created by Matt Bruce on 4/15/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS +import Combine + +open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{ + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var model: MoleculeModelProtocol? + + public var viewModel: TileContainerModel! + public var delegateObject: MVMCoreUIDelegateObject? + public var additionalData: [AnyHashable: Any]? + + public var molecule: MoleculeViewProtocol? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public convenience required init() { + self.init(frame: .zero) + } + + //-------------------------------------------------- + // MARK: - Public + //-------------------------------------------------- + public func viewModelDidUpdate() { + + if molecule != nil { + molecule?.set(with: viewModel.molecule, delegateObject, additionalData) + } else if let moleculeView = ModelRegistry.createMolecule(viewModel.molecule, delegateObject: delegateObject, additionalData: additionalData) { + addContentView(moleculeView) + } + + padding = viewModel.padding + color = viewModel.color + backgroundEffect = viewModel.backgroundEffect + aspectRatio = viewModel.aspectRatio + width = viewModel.width + height = viewModel.height + showBorder = viewModel.showBorder + showDropShadows = viewModel.showDropShadwows + //setup action + if let action = viewModel.action { + //add the subscriber + onClick = { [weak self] control in + guard let self, let viewModel = self.viewModel else { return } + MVMCoreUIActionHandler.performActionUnstructured(with: action, + sourceModel: viewModel, + additionalData: self.additionalData, + delegateObject: self.delegateObject) + } + } + } + + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + open func updateView(_ size: CGFloat) {} + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + //since this is a class func, we can't reference it directly + public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + 100 + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + } + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift new file mode 100644 index 00000000..bb20d38d --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift @@ -0,0 +1,99 @@ +// +// TileContainerModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 4/15/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +open class TileContainerModel: TileContainerBaseModel, MoleculeModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "tileContainer" + public var id: String = UUID().uuidString + public var backgroundColor: Color? + public var padding: TileContainer.Padding = .padding4X + public var aspectRatio: TileContainer.AspectRatio = .ratio1x1 + public var color: TileContainer.BackgroundColor = .secondary + public var backgroundEffect: TileContainer.BackgroundEffect = .none + public var molecule: MoleculeModelProtocol + + private enum CodingKeys: String, CodingKey { + case id + case moleculeName + case molecule + case padding + case aspectRatio + case color + case backgroundEffect + } + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString + molecule = try container.decodeModel(codingKey: .molecule) + padding = try container.decodeIfPresent(TileContainer.Padding.self, forKey: .padding) ?? .padding4X + color = try container.decodeIfPresent(TileContainer.BackgroundColor.self, forKey: .color) ?? .secondary + backgroundEffect = try container.decodeIfPresent(TileContainer.BackgroundEffect.self, forKey: .backgroundEffect) ?? .none + aspectRatio = try container.decodeIfPresent(TileContainer.AspectRatio.self, forKey: .aspectRatio) ?? .none + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(padding, forKey: .padding) + try container.encodeIfPresent(color, forKey: .color) + try container.encodeIfPresent(backgroundEffect, forKey: .backgroundEffect) + try container.encodeIfPresent(aspectRatio, forKey: .aspectRatio) + try super.encode(to: encoder) + } +} + + +open class TileContainerBaseModel: Codable { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var backgroundImage: String? + public var action: ActionModelProtocol? + public var imageFallbackColor: Surface = .light + public var width: CGFloat? + public var height: CGFloat? + public var showBorder: Bool = false + public var showDropShadwows: Bool = false + + private enum CodingKeys: String, CodingKey { + case backgroundImage + case action + case padding + case imageFallbackColor + case width + case height + case showBorder + case showDropShadows + } + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + backgroundImage = try container.decodeIfPresent(String.self, forKey: .backgroundImage) + width = try container.decodeIfPresent(CGFloat.self, forKey: .width) + height = try container.decodeIfPresent(CGFloat.self, forKey: .height) + action = try container.decodeModelIfPresent(codingKey: .action) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundImage, forKey: .backgroundImage) + try container.encodeIfPresent(width, forKey: .width) + try container.encodeIfPresent(height, forKey: .height) + try container.encodeModelIfPresent(action, forKey: .action) + } + + +} diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 19efa0a9..0cd9ba2e 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -100,6 +100,54 @@ extension VDS.TileContainerBase.BackgroundColor: Codable { } } +extension VDS.TileContainerBase.BackgroundEffect: Codable { + enum Error: Swift.Error { + case valueNotFound(type: String) + } + enum CodingKeys: String, CodingKey { + case type + case firstColor + case secondColor + } + + enum BackgroundEffectType: String, Codable { + case transparency + case none + case gradient + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(BackgroundEffectType.self, forKey: .type) + + switch type { + case .transparency: + self = .transparency + 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) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .transparency: + try container.encode(BackgroundEffectType.transparency.rawValue, forKey: .type) + case .none: + 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) + @unknown default: break + } + } +} + extension VDS.TileContainer.Padding: Codable { enum PaddingError: Error { case valueNotFound(type: String)