Digital ACT192 story ONEAPP-8912- YIR | Year in review Prototype Updates

This commit is contained in:
Nowfal E Salam 2024-07-01 00:05:21 +05:30
commit d3bf1d320f
60 changed files with 245 additions and 199 deletions

View File

@ -293,6 +293,7 @@
AF1C336F2885A16A006B1001 /* ActionCollapseNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336E2885A16A006B1001 /* ActionCollapseNotificationHandler.swift */; };
AF1C33712885AE76006B1001 /* MVMCoreUIActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33702885AE76006B1001 /* MVMCoreUIActionHandler.swift */; };
AF1C33732885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */; };
AF1CEFE82BEA73890001F9A5 /* VDSCoreTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF1CEFE72BEA73890001F9A5 /* VDSCoreTokens.xcframework */; };
AF60A7F62892D2E300919EEB /* ActionDismissNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */; };
AF60A7F82892D34D00919EEB /* ActionDismissNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */; };
AF766D262A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF766D252A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift */; };
@ -607,7 +608,6 @@
EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* FormLabel.swift */; };
EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* FormLabelModel.swift */; };
EACCF38C2ABB346700E0F104 /* VDS-Interpreters.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACCF38B2ABB346700E0F104 /* VDS-Interpreters.swift */; };
EAD715AA2BBC8FAF00DEDA6A /* VDSTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAD715A92BBC8FAF00DEDA6A /* VDSTokens.xcframework */; };
FD99130028E21E4900542CC3 /* RuleNotEqualsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */; };
/* End PBXBuildFile section */
@ -911,6 +911,7 @@
AF1C336E2885A16A006B1001 /* ActionCollapseNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCollapseNotificationHandler.swift; sourceTree = "<group>"; };
AF1C33702885AE76006B1001 /* MVMCoreUIActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIActionHandler.swift; sourceTree = "<group>"; };
AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIActionOpenPageHandler.swift; sourceTree = "<group>"; };
AF1CEFE72BEA73890001F9A5 /* VDSCoreTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSCoreTokens.xcframework; path = ../SharedFrameworks/VDSCoreTokens.xcframework; sourceTree = "<group>"; };
AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDismissNotificationModel.swift; sourceTree = "<group>"; };
AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDismissNotificationHandler.swift; sourceTree = "<group>"; };
AF766D252A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAccessibilityTraits+Codable.swift"; sourceTree = "<group>"; };
@ -1244,9 +1245,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AF1CEFE82BEA73890001F9A5 /* VDSCoreTokens.xcframework in Frameworks */,
D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */,
EA985C602970A3F000F2FF2E /* VDS.framework in Frameworks */,
EAD715AA2BBC8FAF00DEDA6A /* VDSTokens.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2138,6 +2139,7 @@
D29DF0E421E4F3C7003B2FB9 /* Frameworks */ = {
isa = PBXGroup;
children = (
AF1CEFE72BEA73890001F9A5 /* VDSCoreTokens.xcframework */,
EAD715A92BBC8FAF00DEDA6A /* VDSTokens.xcframework */,
EA985C5F2970A3F000F2FF2E /* VDS.framework */,
D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */,

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
import MVMCore

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
open class Link: VDS.TextLink, VDSMoleculeViewProtocol {

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
import MVMCore
import Combine

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
@objcMembers open class RadioButton: Control, MFButtonProtocol {
//--------------------------------------------------

View File

@ -41,7 +41,9 @@ open class ButtonIcon: VDS.ButtonIcon, VDSMoleculeViewProtocol {
iconName = viewModel.iconName
selectedIconName = viewModel.selectedIconName
size = viewModel.size
customSize = viewModel.customSize
customContainerSize = viewModel.customContainerSize
customIconSize = viewModel.customIconSize
customBadgeIndicatorOffset = viewModel.customBadgeIndicatorOffSet
floating = viewModel.floating
fitToIcon = viewModel.fitToIcon
hideBorder = viewModel.hideBorder

View File

@ -31,7 +31,9 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
public var iconName: Icon.Name = .info
public var selectedIconName: Icon.Name?
public var size = ButtonIcon.Size.large
public var customSize : Int?
public var customContainerSize : Int?
public var customIconSize : Int?
public var customBadgeIndicatorOffSet : CGPoint?
public var floating: Bool = false
public var fitToIcon: Bool = false
public var hideBorder: Bool = true
@ -85,7 +87,9 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
case iconName
case selectedIconName
case size
case customSize
case customContainerSize
case customIconSize
case customBadgeIndicatorOffSet
case floating
case fitToIcon
case hideBorder
@ -110,7 +114,9 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
iconName = try container.decode(Icon.Name.self, forKey: .iconName)
selectedIconName = try container.decodeIfPresent(Icon.Name.self, forKey: .selectedIconName)
size = try container.decodeIfPresent(ButtonIcon.Size.self, forKey: .size) ?? .large
customSize = try container.decodeIfPresent(Int.self, forKey: .customSize)
customContainerSize = try container.decodeIfPresent(Int.self, forKey: .customContainerSize)
customIconSize = try container.decodeIfPresent(Int.self, forKey: .customIconSize)
customBadgeIndicatorOffSet = try container.decodeIfPresent(CGPoint.self, forKey: .customBadgeIndicatorOffSet)
floating = try container.decodeIfPresent(Bool.self, forKey: .floating) ?? false
fitToIcon = try container.decodeIfPresent(Bool.self, forKey: .fitToIcon) ?? false
hideBorder = try container.decodeIfPresent(Bool.self, forKey: .hideBorder) ?? false
@ -131,7 +137,9 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
try container.encode(iconName, forKey: .iconName)
try container.encodeIfPresent(selectedIconName, forKey: .selectedIconName)
try container.encodeIfPresent(size, forKey: .size)
try container.encodeIfPresent(customSize, forKey: .customSize)
try container.encodeIfPresent(customContainerSize, forKey: .customContainerSize)
try container.encodeIfPresent(customIconSize, forKey: .customIconSize)
try container.encodeIfPresent(customBadgeIndicatorOffSet, forKey: .customBadgeIndicatorOffSet)
try container.encodeIfPresent(floating, forKey: .floating)
try container.encodeIfPresent(fitToIcon, forKey: .fitToIcon)
try container.encodeIfPresent(hideBorder, forKey: .hideBorder)

View File

@ -5,7 +5,7 @@
// Created by Kevin Christiano on 1/30/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDSTokens
import VDSCoreTokens
open class CarouselIndicator: Control, CarouselPageControlProtocol {
//--------------------------------------------------

View File

@ -7,7 +7,7 @@
//
import Foundation
import VDSTokens
import VDSCoreTokens
open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol, EnableableModelProtocol {
//--------------------------------------------------

View File

@ -8,7 +8,7 @@
import Foundation
import VDS
import VDSTokens
import VDSCoreTokens
open class IconModel: MoleculeModelProtocol {

View File

@ -375,8 +375,8 @@ extension Label {
public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect {
guard let abstractContainer = label.abstractTextContainer() else { return CGRect() }
let textContainer = abstractContainer.0
let layoutManager = abstractContainer.1
let textContainer = abstractContainer.textContainer
let layoutManager = abstractContainer.layoutManager
var glyphRange = NSRange()
@ -385,36 +385,6 @@ extension Label {
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
/**
Provides a text container and layout manager of how the text would appear on screen.
They are used in tandem to derive low-level TextKit results of the label.
*/
public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? {
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
guard let attributedText = attributedText else { return nil }
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = textAlignment
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
textContainer.size = bounds.size
return (textContainer, layoutManager, textStorage)
}
}
// MARK: - Atomization

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
public class LineModel: MoleculeModelProtocol, Invertable {

View File

@ -34,6 +34,7 @@ open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, Til
case moleculeName
case molecule
}
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
@ -84,6 +85,7 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
case aspectRatio
case backgroundEffect
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
@ -98,7 +100,6 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
color = try container.decodeIfPresent(TileContainerType.BackgroundColor.self, forKey: .color) ?? .black
aspectRatio = try container.decodeIfPresent(TileContainerType.AspectRatio.self, forKey: .aspectRatio) ?? .ratio1x1
backgroundEffect = try container.decodeIfPresent(TileContainerType.BackgroundEffect.self, forKey: .backgroundEffect) ?? .none
}
public func encode(to encoder: Encoder) throws {

View File

@ -8,7 +8,7 @@
import Foundation
import VDS
import VDSTokens
import VDSCoreTokens
import MVMCore
open class TooltipModel: MoleculeModelProtocol {

View File

@ -8,7 +8,7 @@
import Foundation
import VDS
import VDSTokens
import VDSCoreTokens
//--------------------------------------------------
// MARK: - Codable Extensions

View File

@ -6,7 +6,7 @@
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import VDSTokens
import VDSCoreTokens
import VDS
public class TitleLockupModel: ParentMoleculeModelProtocol {

View File

@ -5,7 +5,7 @@
// Created by Scott Pfeil on 5/28/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDSTokens
import VDSCoreTokens
@objcMembers open class TabBar: UITabBar, MoleculeViewProtocol, TabBarProtocol, UITabBarDelegate {

View File

@ -7,7 +7,7 @@
//
import Foundation
import VDSTokens
import VDSCoreTokens
open class TabBarModel: MoleculeModelProtocol {
public static var identifier: String = "tabBar"
@ -34,7 +34,7 @@ open class TabBarModel: MoleculeModelProtocol {
if let selectedColor = _selectedColor { return selectedColor }
if let style = style,
style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) }
return Color(uiColor: VDSColor.elementsPrimaryOnlight)
return Color(uiColor: UIColor.mvmRed)
}
set {
_selectedColor = newValue
@ -54,7 +54,7 @@ open class TabBarModel: MoleculeModelProtocol {
}
}
open var style: NavigationItemStyle? = .dark
open var style: NavigationItemStyle? = .light
// Must be capped to 0...(tabs.count - 1)
open var selectedTab: Int = 0

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
@objc public protocol TabsDelegate {

View File

@ -7,7 +7,7 @@
//
import UIKit
import VDSTokens
import VDSCoreTokens
import VDS
open class TabsModel: MoleculeModelProtocol {

View File

@ -24,6 +24,7 @@
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var gone: Bool = false
//--------------------------------------------------
// MARK: - Validation
@ -53,6 +54,7 @@
case groupName
case enabled
case readOnly
case gone
}
//--------------------------------------------------
@ -76,6 +78,7 @@
if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) {
self.readOnly = readOnly
}
gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) ?? false
try super.init(from: decoder)
}
@ -99,6 +102,7 @@
&& fieldValue == model.fieldValue
&& enabled == model.enabled
&& readOnly == model.readOnly
&& gone == model.gone
}
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
@ -107,5 +111,6 @@
&& peakingArrowColor == model.peakingArrowColor
&& enabled == model.enabled
&& readOnly == model.readOnly
&& gone == model.gone
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDSTokens
import VDSCoreTokens
public enum NavigationItemStyle: String, Codable {
case light

View File

@ -10,7 +10,7 @@ import Foundation
import Combine
import Dispatch
import MVMCore
import VDSTokens
import VDSCoreTokens
@objcMembers open class CollapsableNotification: View {
//--------------------------------------------------

View File

@ -90,7 +90,7 @@ 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 else { return }
guard let model = model as? CarouselModel, !model.visibleMolecules.isEmpty else { return }
guard (model.paging == true || loop == true) else {
DispatchQueue.main.async { [self] in
updatePagerVisibility()
@ -174,9 +174,12 @@ open class Carousel: View {
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
if #available(iOS 15.0, *) {
if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel) {
if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel),
originalModel.visibleMolecules.isVisuallyEquivalent(to: molecules ?? []) // Since the carousel model's children are in place replaced and we do not have a deep copy of this model tree, add in this hack to check if the prior captured carousel items match the newly visible ones.
{
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
prepareMolecules(with: carouselModel)
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)
updateModelIndex() // Ensure the new model indexing matches the old.
pagingView?.currentIndex = pageIndex // Trigger a paging view render.
@ -228,7 +231,7 @@ open class Carousel: View {
//--------------------------------------------------
func prepareMolecules(with carouselModel: CarouselModel?) {
guard let newMolecules = carouselModel?.molecules else {
guard let newMolecules = carouselModel?.visibleMolecules else {
numberOfPages = 0
molecules = nil
return
@ -304,6 +307,7 @@ open class Carousel: View {
pagingView?.removeFromSuperview()
bottomPin?.isActive = false
pagingView = nil
guard var pagingView = view else {
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)

View File

@ -44,6 +44,10 @@ import UIKit
public var selectable = false
public var selectedIndex: Int?
public var visibleMolecules: [MoleculeModelProtocol & CarouselItemModelProtocol] {
molecules.filter { !$0.gone }
}
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
self.molecules = molecules
}

View File

@ -9,6 +9,7 @@
public protocol CarouselItemModelProtocol: FormFieldProtocol, ContainerModelProtocol {
var analyticsData: JSONValueDictionary? { get set }
var gone: Bool { get set }
}
public extension CarouselItemModelProtocol {
@ -16,4 +17,9 @@ public extension CarouselItemModelProtocol {
get { nil }
set { analyticsData = newValue }
}
var gone: Bool{
get { false }
set { }
}
}

View File

@ -43,7 +43,7 @@ public extension ParentModelProtocol where Self: AnyObject {
func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
var replacedMolecule: MoleculeModelProtocol?
return try replaceChildMolecule(at: &molecules, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil
return try replaceChildMolecule(in: &molecules, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil
}
/// Helper for replacing a molecule in place within an array. Note the "in".
@ -67,6 +67,26 @@ public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelP
public extension ParentMoleculeModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
mutating func deepReplaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
var replacedMolecule: MoleculeModelProtocol?
var possibleError: Error?
// Dive into each root.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do {
replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch {
possibleError = error
}
stop = replacedMolecule != nil || possibleError != nil
}
if let error = possibleError {
throw error
}
return replacedMolecule
}
func reduceDepthFirstTraverse<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result {
var result = initialResult
if (options == .parentFirst) {

View File

@ -40,31 +40,3 @@ public extension TemplateModelProtocol {
return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
}
}
extension TemplateModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
// Attempt root level replacement on the template model first.
if let replacedMolecule = try replaceChildMolecule(with: replacementMolecule) {
return replacedMolecule
}
var replacedMolecule: MoleculeModelProtocol?
var possibleError: Error?
// Dive into each root thereafter.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do {
replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch {
possibleError = error
}
stop = replacedMolecule != nil || possibleError != nil
}
if let error = possibleError {
throw error
}
return replacedMolecule
}
}

View File

@ -83,6 +83,9 @@ import Foundation
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
if let defaultPageType = Self.defaultPageType() {
pageType = try typeContainer.decodeIfPresent(String.self, forKey: .pageType) ?? defaultPageType
if pageType.isEmpty {
pageType = defaultPageType
}
} else {
pageType = try typeContainer.decode(String.self, forKey: .pageType)
}

View File

@ -79,10 +79,10 @@
}
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
open override func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
setup()
registerCells()
super.handleNewData(pageModel)
super.handleNewData(pageModel, shouldTriggerRender: shouldTriggerRender)
}
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {

View File

@ -25,8 +25,8 @@ open class ModalMoleculeListTemplate: MoleculeListTemplate {
try decoder.decode(ModalListPageTemplateModel.self, from: data)
}
override open func handleNewData(_ pageModel: PageModelProtocol? = nil) {
super.handleNewData(pageModel)
override open func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
super.handleNewData(pageModel, shouldTriggerRender: shouldTriggerRender)
closeButton = MVMCoreUICommonViewsUtility.addCloseButton(to: view, tintColor: self.pageModel?.navigationBar?.tintColor.uiColor, action: { [weak self] _ in
guard let self = self else { return }

View File

@ -23,8 +23,8 @@ open class ModalMoleculeStackTemplate: MoleculeStackTemplate {
// MARK: - Lifecycle
//--------------------------------------------------
override open func handleNewData(_ pageModel: PageModelProtocol? = nil) {
super.handleNewData(pageModel)
override open func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
super.handleNewData(pageModel, shouldTriggerRender: shouldTriggerRender)
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, tintColor: self.pageModel?.navigationBar?.tintColor.uiColor, action: { [weak self] _ in
guard let self = self else { return }
let closeAction = (self.templateModel as? ModalStackPageTemplateModel)?.closeAction ??

View File

@ -85,8 +85,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
return view
}
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
super.handleNewData(pageModel)
open override func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
super.handleNewData(pageModel, shouldTriggerRender: shouldTriggerRender)
if pageModel != nil {
setup()

View File

@ -20,10 +20,10 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol {
// MARK: - Lifecycle
//--------------------------------------------------
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
open override func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
topViewOutsideOfScroll = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false
super.handleNewData(pageModel)
super.handleNewData(pageModel, shouldTriggerRender: shouldTriggerRender)
}
// For subclassing the model.

View File

@ -52,17 +52,20 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController, Rotor
bottomView.updateView(width)
showFooter(width)
}
tableView.reloadData()
tableView.visibleCells.forEach { cell in
(cell as? MVMCoreViewProtocol)?.updateView(width)
}
}
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.updateUI(for: molecules)
guard molecules == nil else { return }
createViewForTableHeader()
createViewForTableFooter()
tableView?.reloadData()
// Reloading the table is handled in updateViews, however, update views is on a separate rendering task than the current thread. The table render needs to be bound and settled to the new model before others put in additional update requests.
tableView.reloadData()
}
override open func viewDidLoad() {

View File

@ -146,6 +146,11 @@ import MVMCore
do {
let template = try parsePageJSON(loadObject: loadObject)
pageModel = template // TODO: Eventually this page parsing should be done outside of this class and then set by the caller. For now, double duty.
// Needed for PageMoleculeTransformationBehavior + PageLocalDataShareBehavior behaviors.
if let behaviorContainer = template as? (PageBehaviorContainerModelProtocol & TemplateModelProtocol) {
var behaviorHandler = self
behaviorHandler.applyBehaviors(pageBehaviorModel: behaviorContainer)
}
isFirstRender = true // Assuming this is only on the first page load from the handler. Might need to revist later.
if let backgroundRequest = loadObject.requestParameters?.backgroundRequest, !backgroundRequest, let pageType, let identifier = loadObject.identifier {
MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageProcessingComplete(pageType: pageType, requestUUID: identifier, webUrl: nil))
@ -225,16 +230,16 @@ import MVMCore
return navigationModel
}
/// Processes any new data. Called after the page is loaded the first time and on response updates for this page. Triggers a render refresh.
/// Processes any new data. Called after the page is loaded the first time and on response updates for this page. Triggers a render refresh unless specified otherwise.
@MainActor
open func handleNewData(_ pageModel: PageModelProtocol? = nil) {
open func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
guard var newPageModel = pageModel ?? self.pageModel else { return }
let originalModel = isFirstRender ? nil : self.pageModel as? MVMControllerModelProtocol
let originalModel = self.pageModel as? MVMControllerModelProtocol
// Refresh our behaviors if there is a page change.
// Refresh our behaviors if there is a page change. Originally set up in shouldFinishProcessingLoad.
if let behaviorContainer = newPageModel as? (PageBehaviorContainerModelProtocol & TemplateModelProtocol),
(originalModel == nil || originalModel!.id != behaviorContainer.id) {
(originalModel == nil || originalModel!.id != behaviorContainer.id) {
var behaviorHandler = self
behaviorHandler.applyBehaviors(pageBehaviorModel: behaviorContainer)
}
@ -277,15 +282,20 @@ import MVMCore
let allUpdatedMolecules = behaviorUpdatedModels //+ pageUpdatedModels
// Notify the manager of new data.
// Warning: Some flows cause table reloads. Until the UI update is decoupled, should be after the updateUI.
manager?.newDataReceived?(in: self)
if allUpdatedMolecules.isEmpty || isFirstRender {
debugLog("Performing full page render...")
updateUI()
} else {
debugLog("Performing partial render of \(allUpdatedMolecules) molecules...")
updateUI(for: allUpdatedMolecules)
guard shouldTriggerRender else { return }
// Dispatch to decouple execution. First massage data through template classes, then render.
Task { @MainActor in
if allUpdatedMolecules.isEmpty || isFirstRender {
debugLog("Performing full page render...")
updateUI()
} else {
debugLog("Performing partial render of \(allUpdatedMolecules) molecules...")
updateUI(for: allUpdatedMolecules)
}
}
}
@ -399,7 +409,8 @@ import MVMCore
initialLoad()
}
handleNewData(pageModel) // Set outside shouldFinishProcessingLoad.
handleNewData(pageModel, shouldTriggerRender: false) // Set outside shouldFinishProcessingLoad.
updateUI() // Force the rendering on the same main UI thread.
}
open override func viewDidLayoutSubviews() {

View File

@ -41,7 +41,7 @@ public extension PageBehaviorHandlerProtocol {
// Apply them to the page.
self.behaviors = behaviors.count > 0 ? behaviors : nil
// Ask the session to apply any more. (Curently inverted contol due to Swift <--> Obj-C conflict.
// Ask the session to apply any more. (Currently inverted contol due to Swift <--> Obj-C conflict.)
if let viewController = self as? UIViewController {
MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController)
}

View File

@ -20,7 +20,7 @@ public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol {
}
}
public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, CoreLogging {
public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, PageVisibilityBehavior, CoreLogging {
public var loggingPrefix: String {
"\(self) \(ObjectIdentifier(self).hashValue) \(moleculeIds.prefix(3)) \(moleculeIds.count > 3 ? "+ \(moleculeIds.count - 3) more" : ""):\n"
@ -30,6 +30,8 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
String(describing: Self.self)
}
var isPageShowing = false
var previouslyReplacedIds = Set<String>()
var moleculeIds: [String]
public var modulesToListenFor: [String]
private var observingForResponses: NSObjectProtocol?
@ -41,7 +43,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
self.delegateObject = delegateObject
guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return }
MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType)
Self.debugLog("Initializing for \((model as! ReplaceableMoleculeBehaviorModel).moleculeIds)")
Self.debugLog("Initializing for \(moleculeIds)")
}
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]? {
@ -75,7 +77,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
return rootMolecule
}
debugLog("top replacing \(rootMolecule) with \(updatedMolecule)")
logUpdated(molecule: updatedMolecule)
logUpdated(moleculeId: updatedMolecule.id)
changeList.append(updatedMolecule)
return updatedMolecule
}
@ -85,14 +87,14 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
moleculeModels.forEach { newMolecule in
do {
if let replacedMolecule = try parentMolecule.replaceChildMolecule(with: newMolecule) {
if let replacedMolecule = try parentMolecule.deepReplaceMolecule(with: newMolecule) {
guard !replacedMolecule.deepEquals(to: newMolecule) else {
// Note: Slight risk here of replacing the something in the original tree and misreporting that is it not replaced based on equality.
debugLog("deep molecule \(newMolecule) is the same as \(replacedMolecule). skipping...")
return
}
debugLog("deep replacing \(replacedMolecule) with \(newMolecule)")
logUpdated(molecule: newMolecule)
logUpdated(moleculeId: newMolecule.id)
changeList.append(newMolecule)
}
} catch {
@ -111,15 +113,29 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
return hasReplacement ? updatedRootMolecules : nil
}
private func logUpdated(molecule: MoleculeModelProtocol) {
guard let module: [AnyHashable: Any] = delegateObject?.moleculeDelegate?.getModuleWithName(molecule.id),
private func logUpdated(moleculeId: String) {
guard let module: [AnyHashable: Any] = delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId),
let viewController = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else {
debugLog("Missing the originating module \(molecule.id) creating this molecule!")
debugLog("Missing the originating module \(moleculeId) creating this molecule!")
return
}
previouslyReplacedIds.insert(moleculeId)
guard isPageShowing else { return } // Page has not been made visible yet. (Pulled and replaced from cache on load or background update.) Hold reporting until onPageShown.
MVMCoreUILoggingHandler.shared()?.defaultLogPageUpdate(forController: viewController, from: module)
}
public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) {
isPageShowing = true
debugLog("Page shown. Send molecule analytics for: \(previouslyReplacedIds)")
previouslyReplacedIds.forEach { id in
logUpdated(moleculeId: id)
}
}
public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) {
isPageShowing = false
}
deinit {
debugLog("deinit")
}

View File

@ -7,6 +7,7 @@
//
import UIKit
import VDSCoreTokens
public typealias ColorHexTuple = (uiColor: UIColor, hex: String)
@ -19,7 +20,8 @@ extension UIColor {
/// Dictionary to access brand approved colors by name.
public static let names: [String: ColorHexTuple] = ["black": (.mvmBlack, "#000000"),
"white": (.mvmWhite, "#FFFFFF"),
"red": (.mvmRed, "#D52B1E"),
"red": (.mvmRed, "#EE0000"),
"monarchRed": (VDSCoreTokens.VDSColor.paletteMonarchred, "#f50a23"),
"pink": (.mvmPink, "#D90368"),
"pink33": (.mvmPink33, "#F2ABCD"),
"pink66": (.mvmPink66, "#E6589B"),
@ -48,13 +50,20 @@ extension UIColor {
"blueShade2": (.mvmBlueShade2, "#0B4467"),
"blueInverted": (.mvmBlueInverted, "#0088CE"),
"yellow": (.mvmYellow, "#FFBC3D"),
"neonYellow": (VDSCoreTokens.VDSColor.paletteNeonyellow, "#f5ff1e"),
"coolGray1": (.mvmCoolGray1, "#F6F6F6"),
"coolGray3": (.mvmCoolGray3, "#D8DADA"),
"coolGray6": (.mvmCoolGray6, "#747676"),
"coolGray10": (.mvmCoolGray10, "#333333"),
"upGold1": (.vzupGold1, "#F9D542"),
"upGold2": (.vzupGold2, "#F4CA53"),
"upGold3": (.vzupGold3, "#CC9B2D")]
"upGold3": (.vzupGold3, "#CC9B2D"),
"stone": (.stone, "#F3EDE0"),
"coral": (.coral, "#FF3C2D"),
"gray44": (.gray44, "#6F7171"),
"gray85": (.gray85, "#D8DADA"),
"gray95": (.gray95, "#F6F6F6")
]
//--------------------------------------------------
// MARK: - Helper
@ -82,9 +91,10 @@ extension UIColor {
// MARK: - Red
//--------------------------------------------------
/// HEX: #D52B1E
/// HEX: #EE0000
@objc
public static let mvmRed = UIColor.assetColor(named: "red")
//--------------------------------------------------
// MARK: - Pink
//--------------------------------------------------
@ -190,7 +200,7 @@ extension UIColor {
// MARK: - Yellow
//--------------------------------------------------
/// HEX: #FFBC3D
/// HEX: ##F5FF1E
public static let mvmYellow = UIColor.assetColor(named: "yellow")
//--------------------------------------------------
@ -222,6 +232,25 @@ extension UIColor {
/// HEX: #CC9B2D
public static let vzupGold3 = UIColor.assetColor(named: "upGold3")
//--------------------------------------------------
// MARK: - Monarch
//--------------------------------------------------
/// HEX: #F3EDE0
@objc public static let stone = VDSCoreTokens.VDSColor.paletteStone
// HEX:#FF3C2D
@objc public static let coral = VDSCoreTokens.VDSColor.paletteCoral
// HEX:#6F7171
@objc public static let gray44 = VDSCoreTokens.VDSColor.paletteGray44
// HEX:#D8DADA
@objc public static let gray85 = VDSCoreTokens.VDSColor.paletteGray85
// HEX:#F6F6F6
@objc public static let gray95 = VDSCoreTokens.VDSColor.paletteGray95
//--------------------------------------------------
// MARK: - Functions
//--------------------------------------------------

View File

@ -77,7 +77,7 @@
#pragma mark - legacy
+ (nonnull UIColor *)mfRedColor {
return [UIColor colorWithRed:.804 green:.016 blue:.043 alpha:1.0];
return [UIColor mvmRed];
}
+ (nonnull UIColor *)mfDarkerRedColor {
@ -307,8 +307,6 @@
static dispatch_once_t once;
dispatch_once(&once, ^{
stringColorMapping = @{@"PrimaryRed":[UIColor mfRedColor],
@"black":[UIColor blackColor],
@"red":[UIColor mfRedColor],
@"greyish":[UIColor mfLightGrayColor],
@"robinsEggBlue" : [UIColor mfRobinsEggBlue],
@"lightSalmon" : [UIColor mfLightSalmon],

View File

@ -1,20 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0xD5",
"alpha" : "1.000",
"blue" : "0x1E",
"green" : "0x2B"
"blue" : "0x00",
"green" : "0x00",
"red" : "0xEE"
}
}
},
"idiom" : "universal"
}
]
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,20 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0xFF",
"alpha" : "1.000",
"blue" : "0x3D",
"green" : "0xBC"
"green" : "0xBC",
"red" : "0xFF"
}
}
},
"idiom" : "universal"
}
]
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -8,7 +8,7 @@
import Foundation
import MVMCore
import VDSTokens
import VDSCoreTokens
open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol {
/// The number of tabs count or less that will turn on the fillContainer

View File

@ -1,23 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "exportBlack.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "exportBlack@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "exportBlack@3x.png",
"scale" : "3x"
"filename" : "Vector.svg",
"idiom" : "universal"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
}

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 -3.05176e-05V7.11108H18.7555V2.1333L11.9555 8.9333L11.0666 8.04441L17.8666 1.25553H12.8889V-3.05176e-05H20ZM15 18.7555H1.25553V4.99997H12.2222L13.3333 3.75552H-2.28882e-05V20H16.2555V6.66664L15 7.77775V18.7555Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 869 B

View File

@ -1,23 +1,15 @@
{
"images" : [
{
"filename" : "nav_back.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "nav_back@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "nav_back@3x.png",
"idiom" : "universal",
"scale" : "3x"
"filename" : "Vector.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 10.6361H2.46073L10.9975 19.1001L10.0897 20L0 10L10.0897 0L10.9974 0.899867L2.46077 9.36388H20V10.6361Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

View File

@ -1,23 +1,15 @@
{
"images" : [
{
"filename" : "Close.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Close-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Close-2.png",
"idiom" : "universal",
"scale" : "3x"
"filename" : "Vector.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.87772 9.00001L17.7777 16.8889L16.8888 17.7778L8.99995 9.87778L1.11106 17.7778L0.222168 16.8889L8.11106 9.00001L0.222168 1.11112L1.11106 0.222229L8.99995 8.11112L16.8888 0.222229L17.7777 1.11112L9.87772 9.00001Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@ -22,6 +22,9 @@ NS_ASSUME_NONNULL_BEGIN
// The bundle for this framework
+ (nullable NSBundle *)bundleForMVMCoreUI;
/// The bundle for the VDS frameowrk. Handy for accessing VDS resources such as fonts.
+ (nullable NSBundle *)bundleForFonts;
// Returns the hardcoded string from the string file.
+ (nullable NSString *)hardcodedStringWithKey:(nonnull NSString *)key;

View File

@ -22,6 +22,10 @@
return [NSBundle bundleWithIdentifier:@"com.vzw.MVMCoreUI"];
}
+ (nullable NSBundle *)bundleForFonts {
return [NSBundle bundleWithIdentifier:@"com.vzw.vds"];
}
+ (nullable NSString *)hardcodedStringWithKey:(nonnull NSString *)key {
// Redirect key with relevant module.
return [MVMCoreGetterUtility hardcodedStringWithKey:key bundle:[MVMCoreUIUtility bundleForMVMCoreUI]];