From 5a842581c286daedaba91014ad6e76911adf7a3d Mon Sep 17 00:00:00 2001 From: Rebecca Antonelli Date: Fri, 7 Apr 2023 11:40:26 -0500 Subject: [PATCH 001/112] adding badge VDS component --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +++ MVMCoreUI/Atomic/Atoms/Views/Badge.swift | 50 +++++++++++++++++++ MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 46 +++++++++++++++++ .../OtherHandlers/CoreUIModelMapping.swift | 1 + 4 files changed, 105 insertions(+) create mode 100644 MVMCoreUI/Atomic/Atoms/Views/Badge.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 104eef80..beb77bd9 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -289,6 +289,8 @@ AF60A7F82892D34D00919EEB /* ActionDismissNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; + B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; }; + B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; }; @@ -899,6 +901,8 @@ AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDismissNotificationHandler.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; + B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; + B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; @@ -2281,7 +2285,9 @@ AA37CBD42519072F0027344C /* Stars.swift */, AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, + B4CC8FBC29DF34680005D28B /* Badge.swift */, EA985C3D2970938F00F2FF2E /* Tilelet.swift */, + B4CC8FBE29DF34730005D28B /* BadgeModel.swift */, EA985C3F2970939A00F2FF2E /* TileletModel.swift */, ); path = Views; @@ -3011,6 +3017,7 @@ 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, + B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */, 8D24041523E7FC0B009E23BE /* ListLeftVariableIconWithRightCaretModel.swift in Sources */, 3265B30224BCA737000D154B /* HeadersH1NoButtonsBodyTextModel.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, @@ -3117,6 +3124,7 @@ 0A51F3E32475CB73002E08B6 /* LoadingSpinner.swift in Sources */, BB2FB3BB247E7EBC00DF73CD /* TagCollectionViewCell.swift in Sources */, 012A88C6238DA34000FE3DA1 /* ModuleMoleculeModel.swift in Sources */, + B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */, 94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */, EA985C852981AA9C00F2FF2E /* VDS-Enums+Codable.swift in Sources */, AAB8549824DC01BD00477C40 /* ListThreeColumnBillHistoryDividerModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift new file mode 100644 index 00000000..8ec875c7 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift @@ -0,0 +1,50 @@ +// +// Badge.swift +// MVMCoreUI +// +// Created by Rebecca Antonelli on 4/6/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore +import VDS +import Combine + +open class Badge: VDS.Badge, VDSMoleculeViewProtocol { + // public typealias ViewModel = type + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var viewModel: BadgeModel! + + public var delegateObject: MVMCoreUIDelegateObject? + + public var additionalData: [AnyHashable : Any]? + + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- +// public convenience required init() { +// self.init(frame: .zero) +// } + + //-------------------------------------------------- + // MARK: - Public + //-------------------------------------------------- + public func viewModelDidUpdate() { +// backgroundColor = viewModel.backgroundColor +// maxWidth = viewModel.maxWidth +// numberOfLines = viewModel.numberOfLines +// fillColor = viewModel.fillColor +// surface = viewModel.surface +// + } + + public func updateView(_ size: CGFloat) { + + } + + +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift new file mode 100644 index 00000000..9aa22cd5 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -0,0 +1,46 @@ +// +// BadgeModel.swift +// MVMCoreUI +// +// Created by Rebecca Antonelli on 4/6/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +open class BadgeModel: MoleculeModelProtocol { + + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "badge" + public var backgroundColor: Color? + public var maxWidth: CGFloat? + public var numberOfLines: Int? + public var fillColor = Badge.FillColor.red + public var surface: Surface? + + private enum CodingKeys: String, CodingKey { + case text, fillColor, surface, numberOfLines, maxWidth + } + + required public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let text = try container.decode(String.self, forKey: .text) + let fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red + let surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light + let numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 0 + let maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth) + self.init() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fillColor, forKey: .fillColor) + try container.encode(surface, forKey: .surface) + try container.encode(numberOfLines, forKey: .numberOfLines) + try container.encodeIfPresent(maxWidth, forKey: .maxWidth) + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index c27553ec..f020dcf7 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -72,6 +72,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: LoadingSpinner.self, for: LoadingSpinnerModel.self) ModelRegistry.register(handler: Video.self, for: VideoModel.self) ModelRegistry.register(handler: Tilelet.self, for: TileletModel.self) + ModelRegistry.register(handler: Badge.self, for: BadgeModel.self) // MARK:- Horizontal Combination Molecules ModelRegistry.register(handler: StringAndMoleculeView.self, for: StringAndMoleculeModel.self) From a8dc76e3e5a1831b62b58500eb8205c75ec2e2ac Mon Sep 17 00:00:00 2001 From: Rebecca Antonelli Date: Mon, 10 Apr 2023 10:50:13 -0500 Subject: [PATCH 002/112] badge updates --- MVMCoreUI/Atomic/Atoms/Views/Badge.swift | 37 ++++++++++++++++--- MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 16 +++++--- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift index 8ec875c7..7d6110af 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift @@ -29,22 +29,47 @@ open class Badge: VDS.Badge, VDSMoleculeViewProtocol { // public convenience required init() { // self.init(frame: .zero) // } + public required init() { + super.init() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } //-------------------------------------------------- // MARK: - Public //-------------------------------------------------- + + open func set(){ + maxWidth = viewModel.maxWidth + numberOfLines = viewModel.numberOfLines ?? 3 + fillColor = viewModel.fillColor + surface = viewModel.surface ?? .light + } + public func viewModelDidUpdate() { -// backgroundColor = viewModel.backgroundColor -// maxWidth = viewModel.maxWidth -// numberOfLines = viewModel.numberOfLines -// fillColor = viewModel.fillColor -// surface = viewModel.surface -// + // backgroundColor = viewModel.backgroundColor + maxWidth = viewModel.maxWidth + numberOfLines = viewModel.numberOfLines ?? 3 + fillColor = viewModel.fillColor + surface = viewModel.surface ?? .light + } public func updateView(_ size: CGFloat) { } +// open override func setupView() { +// super.setupView() +// backgroundColor = .clear +// widthConstraint = widthAnchor.constraint(equalToConstant: 30) +// widthConstraint?.isActive = true +// heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) +// heightConstraint?.isActive = true +// isAccessibilityElement = true +// updateAccessibilityLabel() +// } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift index 9aa22cd5..5a5098b0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -21,19 +21,25 @@ open class BadgeModel: MoleculeModelProtocol { public var numberOfLines: Int? public var fillColor = Badge.FillColor.red public var surface: Surface? + public var text: String = "" private enum CodingKeys: String, CodingKey { case text, fillColor, surface, numberOfLines, maxWidth } +// public convenience init() { +// self.init() +// } + required public convenience init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let text = try container.decode(String.self, forKey: .text) - let fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red - let surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light - let numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 0 - let maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth) self.init() + self.text = try container.decode(String.self, forKey: .text) + self.fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red + self.surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light + self.numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 1 + self.maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth) + } public func encode(to encoder: Encoder) throws { From 60d5c45a397e5f64347837bf4421ebc045182a15 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 10 Apr 2023 11:18:30 -0500 Subject: [PATCH 003/112] update view, removed some un-needed code, added other code Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Badge.swift | 53 +++++-------------- MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 28 +++++----- 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift index 7d6110af..0e6d1075 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift @@ -22,54 +22,25 @@ open class Badge: VDS.Badge, VDSMoleculeViewProtocol { public var additionalData: [AnyHashable : Any]? - - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- -// public convenience required init() { -// self.init(frame: .zero) -// } - public required init() { - super.init() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - //-------------------------------------------------- // MARK: - Public //-------------------------------------------------- - - open func set(){ - maxWidth = viewModel.maxWidth - numberOfLines = viewModel.numberOfLines ?? 3 - fillColor = viewModel.fillColor - surface = viewModel.surface ?? .light - } public func viewModelDidUpdate() { - // backgroundColor = viewModel.backgroundColor + text = viewModel.text maxWidth = viewModel.maxWidth - numberOfLines = viewModel.numberOfLines ?? 3 + numberOfLines = viewModel.numberOfLines fillColor = viewModel.fillColor - surface = viewModel.surface ?? .light - + surface = viewModel.surface } - public func updateView(_ size: CGFloat) { - - } - -// open override func setupView() { -// super.setupView() -// backgroundColor = .clear -// widthConstraint = widthAnchor.constraint(equalToConstant: 30) -// widthConstraint?.isActive = true -// heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) -// heightConstraint?.isActive = true -// isAccessibilityElement = true -// updateAccessibilityLabel() -// } - + public func updateView(_ size: CGFloat) {} +} + +//to deal with how it's parent constrains this control +extension Badge: MVMCoreUIViewConstrainingProtocol { + + public func needsToBeConstrained() -> Bool { true } + + public func horizontalAlignment() -> UIStackView.Alignment { .leading } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift index 5a5098b0..de19faab 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -17,33 +17,29 @@ open class BadgeModel: MoleculeModelProtocol { //-------------------------------------------------- public static var identifier: String = "badge" public var backgroundColor: Color? - public var maxWidth: CGFloat? - public var numberOfLines: Int? - public var fillColor = Badge.FillColor.red - public var surface: Surface? public var text: String = "" + public var maxWidth: CGFloat? + public var numberOfLines: Int = 1 + public var fillColor = Badge.FillColor.red + public var surface: Surface = .light private enum CodingKeys: String, CodingKey { case text, fillColor, surface, numberOfLines, maxWidth } - -// public convenience init() { -// self.init() -// } - + required public convenience init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) self.init() - self.text = try container.decode(String.self, forKey: .text) - self.fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red - self.surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light - self.numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 1 - self.maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth) - + let container = try decoder.container(keyedBy: CodingKeys.self) + text = try container.decode(String.self, forKey: .text) + fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red + surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light + numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 1 + maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(text, forKey: .text) try container.encode(fillColor, forKey: .fillColor) try container.encode(surface, forKey: .surface) try container.encode(numberOfLines, forKey: .numberOfLines) From 367f6d45a634e17a1fb7598c6af9d7ae3c602f00 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 22 Jun 2023 20:01:32 +0530 Subject: [PATCH 004/112] Added static test for carousel. Added static test for the first slide alone to a carousel. --- MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index ba70fc2b..a73f972a 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -76,6 +76,7 @@ // MARK: Carousel "MVMCoreUIPageControl_currentpage_index" = "page %@ of %d"; "MVMCoreUIPageControlslides_currentpage_index" = "slide %@ of %d"; +"MVMCoreUIPageControlslides_totalslides" = "Carousel containing %d slides,"; // MARK: Styler From 183d565eaf6617abfc0d7d0d856bf1adc290dff4 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 29 Jun 2023 22:51:27 +0530 Subject: [PATCH 005/112] updated static string text for MVMCoreUIPageControlslides_currentpage_index --- MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index a73f972a..b6728525 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -75,7 +75,7 @@ // MARK: Carousel "MVMCoreUIPageControl_currentpage_index" = "page %@ of %d"; -"MVMCoreUIPageControlslides_currentpage_index" = "slide %@ of %d"; +"MVMCoreUIPageControlslides_currentpage_index" = "slide %@ of %d selected"; "MVMCoreUIPageControlslides_totalslides" = "Carousel containing %d slides,"; From fedd0561753e82241a496a66b465927080b8f798 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 30 Jun 2023 23:58:52 +0530 Subject: [PATCH 006/112] added AccessibilityHandler, MoleculeViewModelProtocol, AccessibilityElementProtocol --- MVMCoreUI.xcodeproj/project.pbxproj | 12 + .../Accessibility/AccessibilityHandler.swift | 223 ++++++++++++++++++ MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 8 +- .../Atomic/Atoms/Selectors/ToggleModel.swift | 6 +- .../HeadlineBodyToggleModel.swift | 4 +- .../Protocols/MoleculeViewProtocol.swift | 5 + .../Protocols/AccessibilityProtocol.swift | 5 + MVMCoreUI/OtherHandlers/CoreUIObject.swift | 9 +- 8 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 MVMCoreUI/Accessibility/AccessibilityHandler.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index f0e4f4f6..06d4c8ef 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,6 +168,7 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; + 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; }; @@ -753,6 +754,7 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; + 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = ""; }; @@ -1449,6 +1451,14 @@ path = OneColumn; sourceTree = ""; }; + 7199C8142A4F3A40001568B7 /* Accessibility */ = { + isa = PBXGroup; + children = ( + 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */, + ); + path = Accessibility; + sourceTree = ""; + }; 8DD1E36C243B3CD900D8F2DF /* ThreeColumn */ = { isa = PBXGroup; children = ( @@ -1972,6 +1982,7 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 7199C8142A4F3A40001568B7 /* Accessibility */, 01F2C1FC27C81F9700DC3D36 /* Managers */, D2ED27D8254B0C1F00A1C293 /* Alerts */, 27F973512466071600CAB5C5 /* Behaviors */, @@ -2827,6 +2838,7 @@ 0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, AA3561AC24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift in Sources */, + 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */, D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */, 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 32D2609624C19E2100B56344 /* LockupsPlanSMLXL.swift in Sources */, diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift new file mode 100644 index 00000000..3ee9c3f7 --- /dev/null +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -0,0 +1,223 @@ +// +// AccessibilityHandler.swift +// MVMCoreUI +// +// Created by Bandaru, Krishna Kishore on 30/06/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import Combine + +public enum AccessibilityNotificationType { + + case controllerChanged, layoutChanged, screenChanged, announcement + + //TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher. + //https://developer.apple.com/forums/thread/132699, + //https://developer.apple.com/forums/thread/655359 + //By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push + var delay: Double { + switch self { + case .controllerChanged: + return 1.5 + case .screenChanged, .layoutChanged: + return 0.0 + default: + return 0.0 + } + } + + var accessibilityNotification: UIAccessibility.Notification { + switch self { + case .announcement: + return .announcement + case .screenChanged: + return .screenChanged + case .layoutChanged, .controllerChanged: + return .layoutChanged + } + } +} + +public class AccessbilityOperation: MVMCoreOperation { + + let argument: Any? + let notificationType: AccessibilityNotificationType + private var timerSource: DispatchSourceTimer? + + public init(notificationType: AccessibilityNotificationType, argument: Any?) { + self.notificationType = notificationType + self.argument = argument + } + + public override func main() { + Task { @MainActor in + guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else { + stop() + return + } + timerSource = DispatchSource.makeTimerSource() + timerSource?.setEventHandler { [weak self] in + if !(self?.isCancelled ?? false), let notification = self?.notificationType.accessibilityNotification { + UIAccessibility.post(notification: notification, argument: self?.argument) + self?.markAsFinished() + } else { + self?.stop() + } + } + timerSource?.schedule(deadline: .now() + notificationType.delay) + timerSource?.activate() + } + } + + public func stop() { + guard isCancelled else { return } + timerSource?.cancel() + markAsFinished() + } +} + +open class AccessibilityHandler { + + public static func shared() -> Self? { + guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } + return MVMCoreActionUtility.fatalClassCheck(object: shared) + } + public let webPageNavigated = PassthroughSubject() + + private var accessibilityOperationQueue: OperationQueue = { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + return queue + }() + private var anyCancellable: Set = [] + private weak var delegate: MVMCoreViewControllerProtocol? + private var accessibilityId: String? + private var operationType: UINavigationController.Operation? + private var previousAccessiblityElement: Any? + + public init() { + registerWithNotificationCenter() + registerForPageChanges() + registerForFocusChanges() +// registerForTopNotificationsChanges() + registerForWebpageNavigation() + } + + /// Registers with the notification center to know when json is updated. + private func registerWithNotificationCenter() { + NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded)) + .sink { [weak self] notification in + self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId") + }.store(in: &anyCancellable) + } + + /// Registers to know when pages change. + private func registerForPageChanges() { + MVMCoreNavigationHandler.shared()?.addDelegate(self) + } + + private func registerForFocusChanges() { + //Since foucs shifted to other elements cancelling existing focus shift notifications if any + NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) + .sink { [weak self] notification in + self?.cancelAllOperations() + }.store(in: &anyCancellable) + } + + private func registerForTopNotificationsChanges() { + NotificationHandler.shared()?.onNotificationShown + .sink { [weak self] (view, model) in + self?.post(notification: .layoutChanged, argument: view) + }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationDismissed + .sink { [weak self] (view, model) in + self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed")) + self?.post(notification: .screenChanged, argument: self?.previousAccessiblityElement) + }.store(in: &anyCancellable) + print(anyCancellable) + } + + private func registerForWebpageNavigation() { + webPageNavigated.sink { [weak self] _ in + self?.post(notification: .controllerChanged, argument: self?.getFirstFocusedElementOnScreen()) + }.store(in: &anyCancellable) + } + + private func add(operation: Operation) { + accessibilityOperationQueue.addOperation(operation) + } + + private func cancelAllOperations() { + accessibilityOperationQueue.cancelAllOperations() + } + + public func post(notification type: AccessibilityNotificationType, argument: Any?) { + guard UIAccessibility.isVoiceOverRunning else { return } + let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) + add(operation: accessbilityOperation) + previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) + } + + //Subclass can decide to trigger Accessibility notification on screen change. + open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } +} + +extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { + + public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { + delegate = viewController as? MVMCoreViewControllerProtocol + operationType = navigationController.viewControllers.contains(viewController) ? .pop : .push + } + + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + //TODO: - For push operation we are posting accessibility notification to the top left item or the identified accessibility element from pageJSON. Need to check the logic for pop operation + guard UIAccessibility.isVoiceOverRunning, + operationType == .push, + canPostAccessbilityNotification(for: viewController) else { return } + let accessbilityElement = getAccessbilityFocusedElement() + post(notification: .controllerChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + } +} + +extension AccessibilityHandler { + + private func getAccessbilityFocusedElement() -> UIView? { + guard let accessibilityModels: [any AccessibilityElementProtocol] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil } + guard !accessibilityModels.isEmpty, + let accessibilityModel = (accessibilityModels.filter { $0.id == accessibilityId }).first as? MoleculeModelProtocol else { + return nil + } + return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in + print("subview: \(subView)") + guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.getMoleculeModel() as? (any AccessibilityElementProtocol), + moleculeModel.id == (accessibilityModel as? (any AccessibilityElementProtocol))?.id else { + return false + } + return true + }.first + } + + //To get first foucs element on the screen + private func getFirstFocusedElementOnScreen() -> Any? { + (delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar + } +} + +extension UIView { + + private func getNestedSubviews() -> [T] { + subviews.flatMap { subView -> [T] in + var result = subView.getNestedSubviews() as [T] + if let view = subView as? T { result.append(view) } + return result + } + } + + func getMoleculeViews(filter: ((T) -> Bool)) -> [T] { + return getNestedSubviews().compactMap { + filter($0) ? $0 : nil + } + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 85ad8e9c..359df799 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -372,7 +372,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - + self.model = model + guard let model = model as? ToggleModel else { return } FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) @@ -418,3 +419,8 @@ extension Toggle { public func horizontalAlignment() -> UIStackView.Alignment { .trailing } } + +extension Toggle: MoleculeViewModelProtocol { + + public func getMoleculeModel() -> MoleculeModelProtocol? { model } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index e8d50851..7336bbad 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -7,7 +7,7 @@ // -public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { +public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, AccessibilityElementProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -30,6 +30,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? + public var id: String? //-------------------------------------------------- // MARK: - Keys @@ -52,6 +53,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { case offKnobTintColor case fieldKey case groupName + case id } //-------------------------------------------------- @@ -124,6 +126,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { } enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + id = try typeContainer.decodeIfPresent(String.self, forKey: .id) } public func encode(to encoder: Encoder) throws { @@ -144,5 +147,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encode(readOnly, forKey: .readOnly) + try container.encodeIfPresent(id, forKey: .id) } } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/HeadlineBodyToggleModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/HeadlineBodyToggleModel.swift index 800c976b..b9b5f81c 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/HeadlineBodyToggleModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/HeadlineBodyToggleModel.swift @@ -9,12 +9,14 @@ import Foundation -open class HeadlineBodyToggleModel: MoleculeModelProtocol { +open class HeadlineBodyToggleModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { public static var identifier: String = "headlineBodyToggle" public var moleculeName: String = HeadlineBodyToggleModel.identifier open var backgroundColor: Color? open var headlineBody: HeadlineBodyModel open var toggle: ToggleModel + + public var children: [MoleculeModelProtocol] { [headlineBody, toggle] } public init(_ headlineBody: HeadlineBodyModel, _ toggle: ToggleModel) { self.headlineBody = headlineBody diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 624f99a6..47e0ca07 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -101,3 +101,8 @@ public extension ModelRegistry { } } } + +public protocol MoleculeViewModelProtocol: UIView { + + func getMoleculeModel() -> MoleculeModelProtocol? +} diff --git a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift index 6c7ada50..d0501874 100644 --- a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift +++ b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift @@ -12,3 +12,8 @@ import Foundation /// Should return the argument to use for posting a layout change. func getAccessibilityLayoutChangedArgument() -> Any? } + +public protocol AccessibilityElementProtocol: Identifiable { + + var id: String? { get set } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index b96e9b93..eaf59289 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -11,8 +11,13 @@ import MVMCore @objcMembers open class CoreUIObject: MVMCoreObject { public var alertHandler: AlertHandler? - public var topNotificationHandler: NotificationHandler? - + public var topNotificationHandler: NotificationHandler? { + didSet { + accessibilityHandler = AccessibilityHandler() + } + } + public var accessibilityHandler: AccessibilityHandler? + open override func defaultInitialSetup() { CoreUIModelMapping.registerObjects() loadHandler = MVMCoreLoadHandler() From 49ba311d26ab7d66834c9b7db3f56ca98f58b55b Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 6 Jul 2023 16:53:59 +0530 Subject: [PATCH 007/112] added delay only for tabbar change & added webPageChanged notificationtype --- .../Accessibility/AccessibilityHandler.swift | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 3ee9c3f7..c1376437 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -8,10 +8,11 @@ import Foundation import Combine +import MVMCore -public enum AccessibilityNotificationType { +public enum AccessibilityNotificationType: String, Codable { - case controllerChanged, layoutChanged, screenChanged, announcement + case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged //TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher. //https://developer.apple.com/forums/thread/132699, @@ -36,6 +37,8 @@ public enum AccessibilityNotificationType { return .screenChanged case .layoutChanged, .controllerChanged: return .layoutChanged + case .webPageChanged: + return .layoutChanged } } } @@ -94,14 +97,13 @@ open class AccessibilityHandler { private var anyCancellable: Set = [] private weak var delegate: MVMCoreViewControllerProtocol? private var accessibilityId: String? - private var operationType: UINavigationController.Operation? private var previousAccessiblityElement: Any? public init() { registerWithNotificationCenter() registerForPageChanges() registerForFocusChanges() -// registerForTopNotificationsChanges() + registerForTopNotificationsChanges() registerForWebpageNavigation() } @@ -141,7 +143,7 @@ open class AccessibilityHandler { private func registerForWebpageNavigation() { webPageNavigated.sink { [weak self] _ in - self?.post(notification: .controllerChanged, argument: self?.getFirstFocusedElementOnScreen()) + self?.post(notification: .layoutChanged, argument: self?.getFirstFocusedElementOnScreen()) }.store(in: &anyCancellable) } @@ -153,7 +155,7 @@ open class AccessibilityHandler { accessibilityOperationQueue.cancelAllOperations() } - public func post(notification type: AccessibilityNotificationType, argument: Any?) { + public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) add(operation: accessbilityOperation) @@ -168,16 +170,17 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { delegate = viewController as? MVMCoreViewControllerProtocol - operationType = navigationController.viewControllers.contains(viewController) ? .pop : .push } public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - //TODO: - For push operation we are posting accessibility notification to the top left item or the identified accessibility element from pageJSON. Need to check the logic for pop operation guard UIAccessibility.isVoiceOverRunning, - operationType == .push, canPostAccessbilityNotification(for: viewController) else { return } + var navigationOperationType: NavigationType = .push + if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" { + navigationOperationType = .set //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. + } let accessbilityElement = getAccessbilityFocusedElement() - post(notification: .controllerChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + post(notification: navigationOperationType == .set ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) } } @@ -190,7 +193,6 @@ extension AccessibilityHandler { return nil } return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in - print("subview: \(subView)") guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.getMoleculeModel() as? (any AccessibilityElementProtocol), moleculeModel.id == (accessibilityModel as? (any AccessibilityElementProtocol))?.id else { return false From 21a45203a33f095db9216717189bcf36e028e7b5 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 6 Jul 2023 20:23:43 +0530 Subject: [PATCH 008/112] added temp fix for updated operation type logic --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index c1376437..729b3881 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -175,12 +175,13 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { guard UIAccessibility.isVoiceOverRunning, canPostAccessbilityNotification(for: viewController) else { return } - var navigationOperationType: NavigationType = .push + //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count + /*var navigationOperationType: NavigationType = .push if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" { - navigationOperationType = .set //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. - } + navigationOperationType = .set + }*/ let accessbilityElement = getAccessbilityFocusedElement() - post(notification: navigationOperationType == .set ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) } } From d9b77289ede6b7d63a1295ced03f75fa96491e2f Mon Sep 17 00:00:00 2001 From: Keerthy Date: Fri, 7 Jul 2023 12:17:47 +0530 Subject: [PATCH 009/112] updated static string text for MVMCoreUIPageControlslides_currentpage_index_accessibilityAnnouncement" --- MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index b6728525..0b736bf9 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -76,6 +76,7 @@ // MARK: Carousel "MVMCoreUIPageControl_currentpage_index" = "page %@ of %d"; "MVMCoreUIPageControlslides_currentpage_index" = "slide %@ of %d selected"; +"MVMCoreUIPageControlslides_currentpage_index_accessibilityAnnouncement" = "slide %@ of %d"; "MVMCoreUIPageControlslides_totalslides" = "Carousel containing %d slides,"; From ebb2c35c555de095950dcf4e3e38345e11188025 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 7 Jul 2023 18:21:27 +0530 Subject: [PATCH 010/112] setting accessibilityId to nil --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 729b3881..3fcb30c1 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -182,6 +182,7 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { }*/ let accessbilityElement = getAccessbilityFocusedElement() post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + accessibilityId = nil } } From 3d556a850cace92b2e056e0dabda02abc05278b0 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 14 Jul 2023 22:46:22 +0530 Subject: [PATCH 011/112] enhancements for top alert post notification for accessibility --- .../Accessibility/AccessibilityHandler.swift | 67 +++++++++++++------ .../NotificationMoleculeModel.swift | 6 +- .../NotificationContainerView.swift | 14 ---- MVMCoreUI/OtherHandlers/CoreUIObject.swift | 6 +- 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 3fcb30c1..a7e0f245 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -9,10 +9,11 @@ import Foundation import Combine import MVMCore +import WebKit public enum AccessibilityNotificationType: String, Codable { - case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged + case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged, webPageLoaded //TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher. //https://developer.apple.com/forums/thread/132699, @@ -20,7 +21,7 @@ public enum AccessibilityNotificationType: String, Codable { //By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push var delay: Double { switch self { - case .controllerChanged: + case .controllerChanged, .webPageLoaded: return 1.5 case .screenChanged, .layoutChanged: return 0.0 @@ -37,7 +38,7 @@ public enum AccessibilityNotificationType: String, Codable { return .screenChanged case .layoutChanged, .controllerChanged: return .layoutChanged - case .webPageChanged: + case .webPageChanged, .webPageLoaded: return .layoutChanged } } @@ -87,7 +88,8 @@ open class AccessibilityHandler { guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } return MVMCoreActionUtility.fatalClassCheck(object: shared) } - public let webPageNavigated = PassthroughSubject() + public weak var delegate: MVMCoreViewControllerProtocol? + public var previousAccessiblityElement: Any? private var accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() @@ -95,16 +97,15 @@ open class AccessibilityHandler { return queue }() private var anyCancellable: Set = [] - private weak var delegate: MVMCoreViewControllerProtocol? private var accessibilityId: String? - private var previousAccessiblityElement: Any? + private var announcementText: String? + private var hasTopNotitificationInPage: Bool = false public init() { registerWithNotificationCenter() registerForPageChanges() registerForFocusChanges() registerForTopNotificationsChanges() - registerForWebpageNavigation() } /// Registers with the notification center to know when json is updated. @@ -112,6 +113,7 @@ open class AccessibilityHandler { NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded)) .sink { [weak self] notification in self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId") + self?.announcementText = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("announcementText") }.store(in: &anyCancellable) } @@ -129,22 +131,31 @@ open class AccessibilityHandler { } private func registerForTopNotificationsChanges() { + NotificationHandler.shared()?.onNotificationWillShow.sink { [weak self] (_, model) in + self?.hasTopNotitificationInPage = true + self?.capturePreviousFocusElement(for: model.molecule) + }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationShown .sink { [weak self] (view, model) in self?.post(notification: .layoutChanged, argument: view) }.store(in: &anyCancellable) - NotificationHandler.shared()?.onNotificationDismissed + NotificationHandler.shared()?.onNotificationWillDismiss .sink { [weak self] (view, model) in self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed")) - self?.post(notification: .screenChanged, argument: self?.previousAccessiblityElement) + }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationDismissed + .sink { [weak self] (view, model) in + self?.postAccessbilityToPrevElement(for: model.molecule) }.store(in: &anyCancellable) print(anyCancellable) } - private func registerForWebpageNavigation() { - webPageNavigated.sink { [weak self] _ in - self?.post(notification: .layoutChanged, argument: self?.getFirstFocusedElementOnScreen()) - }.store(in: &anyCancellable) + open func capturePreviousFocusElement(for model: MoleculeModelProtocol) { + previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) + } + + open func postAccessbilityToPrevElement(for model: MoleculeModelProtocol) { + post(notification: .layoutChanged, argument: previousAccessiblityElement) } private func add(operation: Operation) { @@ -155,11 +166,19 @@ open class AccessibilityHandler { accessibilityOperationQueue.cancelAllOperations() } + open func post(webpageChanged type: AccessibilityNotificationType, argument: Any? = nil) { + post(notification: type, argument: argument) + } + public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) add(operation: accessbilityOperation) - previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) + } + + //To get first foucs element on the screen + open func getFirstFocusedElementOnScreen() -> Any? { + (delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar } //Subclass can decide to trigger Accessibility notification on screen change. @@ -169,7 +188,12 @@ open class AccessibilityHandler { extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { + previousAccessiblityElement = nil delegate = viewController as? MVMCoreViewControllerProtocol + if let announcementText { + let accessbilityOperation = AccessbilityOperation(notificationType: .announcement, argument: announcementText) + add(operation: accessbilityOperation) + } } public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { @@ -180,9 +204,13 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" { navigationOperationType = .set }*/ - let accessbilityElement = getAccessbilityFocusedElement() - post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) - accessibilityId = nil + if hasTopNotitificationInPage { + previousAccessiblityElement = getFirstFocusedElementOnScreen() + } else { + let accessbilityElement = getAccessbilityFocusedElement() + post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + accessibilityId = nil + } } } @@ -202,11 +230,6 @@ extension AccessibilityHandler { return true }.first } - - //To get first foucs element on the screen - private func getFirstFocusedElementOnScreen() -> Any? { - (delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar - } } extension UIView { diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift index d26bb06c..9143e1c3 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift @@ -7,7 +7,7 @@ // -open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol { +open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol, AccessibilityElementProtocol { /** The style of the notification: @@ -35,18 +35,20 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol { public var button: ButtonModel? public var closeButton: NotificationXButtonModel? public var style: NotificationMoleculeModel.Style = .success + public var id: String? //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil) { + public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil, id: String? = nil) { self.headline = headline self.style = style self.backgroundColor = backgroundColor self.body = body self.button = button self.closeButton = closeButton + self.id = id super.init() } diff --git a/MVMCoreUI/Notification/NotificationContainerView.swift b/MVMCoreUI/Notification/NotificationContainerView.swift index 62d05a9d..0fee99e0 100644 --- a/MVMCoreUI/Notification/NotificationContainerView.swift +++ b/MVMCoreUI/Notification/NotificationContainerView.swift @@ -25,16 +25,6 @@ public class NotificationContainerView: UIView { super.init(coder: coder) setupView() } - - /// Posts a layout change with taking the arguments from the view following the AccessibilityProtocol. - private func updateAccessibilityForTopAlert(_ view: UIView) { - // Update accessibility with top alert - var accessibilityArgument: Any? = view - if let view = view as? AccessibilityProtocol { - accessibilityArgument = view.getAccessibilityLayoutChangedArgument() - } - UIAccessibility.post(notification: .layoutChanged, argument: accessibilityArgument) - } } extension NotificationContainerView: NotificationTransitionDelegateProtocol { @@ -56,7 +46,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol { self.superview?.layoutIfNeeded() } completion: { finished in self.superview?.layoutIfNeeded() - self.updateAccessibilityForTopAlert(notification) continuation.resume() } } @@ -64,14 +53,11 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol { @MainActor public func hide(notification: UIView) async { - // accessibility - below line added to notify VI user through voiceover user when the top alert is closed - UIAccessibility.post(notification: .screenChanged, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed")) await withCheckedContinuation { continuation in UIView.animate(withDuration: 0.5) { self.height.isActive = true self.superview?.layoutIfNeeded() } completion: { finished in - UIAccessibility.post(notification: .layoutChanged, argument: nil) self.currentNotificationView?.removeFromSuperview() self.currentNotificationView = nil continuation.resume() diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index eaf59289..7480b3ad 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -11,11 +11,7 @@ import MVMCore @objcMembers open class CoreUIObject: MVMCoreObject { public var alertHandler: AlertHandler? - public var topNotificationHandler: NotificationHandler? { - didSet { - accessibilityHandler = AccessibilityHandler() - } - } + public var topNotificationHandler: NotificationHandler? public var accessibilityHandler: AccessibilityHandler? open override func defaultInitialSetup() { From 646210f1a3917e5086fb6c5dcfb057933e968f9c Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 21 Jul 2023 12:43:15 +0530 Subject: [PATCH 012/112] updated operation type --- .../Accessibility/AccessibilityHandler.swift | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index a7e0f245..63fb3f74 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -21,8 +21,10 @@ public enum AccessibilityNotificationType: String, Codable { //By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push var delay: Double { switch self { - case .controllerChanged, .webPageLoaded: + case .controllerChanged: return 1.5 + case .webPageLoaded: + return 2.0 case .screenChanged, .layoutChanged: return 0.0 default: @@ -44,35 +46,40 @@ public enum AccessibilityNotificationType: String, Codable { } } +public typealias ArgumentHandler = ((NavigationOperationType?) -> Any?) + public class AccessbilityOperation: MVMCoreOperation { - let argument: Any? - let notificationType: AccessibilityNotificationType + private let operationType: NavigationOperationType + private let argumentHandler: ArgumentHandler? + private let notificationType: AccessibilityNotificationType private var timerSource: DispatchSourceTimer? - public init(notificationType: AccessibilityNotificationType, argument: Any?) { + public init(notificationType: AccessibilityNotificationType, operationType: NavigationOperationType = .default, argumentHandler: ArgumentHandler?) { self.notificationType = notificationType - self.argument = argument + self.argumentHandler = argumentHandler + self.operationType = operationType } public override func main() { - Task { @MainActor in - guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else { - stop() - return - } - timerSource = DispatchSource.makeTimerSource() - timerSource?.setEventHandler { [weak self] in + guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else { + stop() + return + } + timerSource = DispatchSource.makeTimerSource() + timerSource?.setEventHandler { + Task { @MainActor [weak self] in if !(self?.isCancelled ?? false), let notification = self?.notificationType.accessibilityNotification { - UIAccessibility.post(notification: notification, argument: self?.argument) + print("argumentHandler \(self?.argumentHandler?(self?.operationType))") + UIAccessibility.post(notification: notification, argument: self?.argumentHandler?(self?.operationType)) self?.markAsFinished() } else { self?.stop() } } - timerSource?.schedule(deadline: .now() + notificationType.delay) - timerSource?.activate() } + timerSource?.schedule(deadline: .now() + notificationType.delay) + timerSource?.activate() } public func stop() { @@ -82,6 +89,8 @@ public class AccessbilityOperation: MVMCoreOperation { } } +public enum NavigationOperationType { case `default`, tab } + open class AccessibilityHandler { public static func shared() -> Self? { @@ -126,15 +135,18 @@ open class AccessibilityHandler { //Since foucs shifted to other elements cancelling existing focus shift notifications if any NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) .sink { [weak self] notification in + print("testing \(UIAccessibility.focusedElement(using: .notificationVoiceOver))") + print("testing \(notification.userInfo)") self?.cancelAllOperations() }.store(in: &anyCancellable) } private func registerForTopNotificationsChanges() { - NotificationHandler.shared()?.onNotificationWillShow.sink { [weak self] (_, model) in - self?.hasTopNotitificationInPage = true - self?.capturePreviousFocusElement(for: model.molecule) - }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationWillShow + .sink { [weak self] (_, model) in + self?.hasTopNotitificationInPage = true + self?.capturePreviousFocusElement(for: model.molecule) + }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationShown .sink { [weak self] (view, model) in self?.post(notification: .layoutChanged, argument: view) @@ -147,7 +159,6 @@ open class AccessibilityHandler { .sink { [weak self] (view, model) in self?.postAccessbilityToPrevElement(for: model.molecule) }.store(in: &anyCancellable) - print(anyCancellable) } open func capturePreviousFocusElement(for model: MoleculeModelProtocol) { @@ -166,13 +177,15 @@ open class AccessibilityHandler { accessibilityOperationQueue.cancelAllOperations() } - open func post(webpageChanged type: AccessibilityNotificationType, argument: Any? = nil) { - post(notification: type, argument: argument) + open func post(webpageChanged type: AccessibilityNotificationType) { + post(notification: type) } - public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) { + public func post(notification type: AccessibilityNotificationType, operationType: NavigationOperationType = .default, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } - let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) + let accessbilityOperation = AccessbilityOperation(notificationType: type, operationType: operationType) { [weak self] in + ($0 == .tab) ? self?.getFirstFocusedElementOnScreen() : argument + } add(operation: accessbilityOperation) } @@ -187,16 +200,17 @@ open class AccessibilityHandler { extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { - public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { + open func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { previousAccessiblityElement = nil delegate = viewController as? MVMCoreViewControllerProtocol if let announcementText { - let accessbilityOperation = AccessbilityOperation(notificationType: .announcement, argument: announcementText) + let accessbilityOperation = AccessbilityOperation(notificationType: .announcement) { _ in announcementText } add(operation: accessbilityOperation) } } - public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + @MainActor + open func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { guard UIAccessibility.isVoiceOverRunning, canPostAccessbilityNotification(for: viewController) else { return } //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count @@ -208,7 +222,8 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { previousAccessiblityElement = getFirstFocusedElementOnScreen() } else { let accessbilityElement = getAccessbilityFocusedElement() - post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + let operationType: NavigationOperationType = navigationController.children.count == 1 ? .tab : .default //TODO: - need to identify the operationType + post(notification: operationType == .tab ? .controllerChanged : .layoutChanged, operationType: operationType, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) accessibilityId = nil } } From 9bbcd6a8eab896f17aa2413d479a03aa733e56a9 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 26 Jul 2023 20:13:08 +0530 Subject: [PATCH 013/112] removed accessibility notification for webpages. --- .../Accessibility/AccessibilityHandler.swift | 54 ++++++------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 63fb3f74..cea6ffc5 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -13,7 +13,7 @@ import WebKit public enum AccessibilityNotificationType: String, Codable { - case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged, webPageLoaded + case controllerChanged, layoutChanged, screenChanged, announcement //TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher. //https://developer.apple.com/forums/thread/132699, @@ -23,10 +23,6 @@ public enum AccessibilityNotificationType: String, Codable { switch self { case .controllerChanged: return 1.5 - case .webPageLoaded: - return 2.0 - case .screenChanged, .layoutChanged: - return 0.0 default: return 0.0 } @@ -40,8 +36,6 @@ public enum AccessibilityNotificationType: String, Codable { return .screenChanged case .layoutChanged, .controllerChanged: return .layoutChanged - case .webPageChanged, .webPageLoaded: - return .layoutChanged } } } @@ -50,15 +44,13 @@ public typealias ArgumentHandler = ((NavigationOperationType?) -> Any?) public class AccessbilityOperation: MVMCoreOperation { - private let operationType: NavigationOperationType - private let argumentHandler: ArgumentHandler? + private let argument: Any? private let notificationType: AccessibilityNotificationType private var timerSource: DispatchSourceTimer? - public init(notificationType: AccessibilityNotificationType, operationType: NavigationOperationType = .default, argumentHandler: ArgumentHandler?) { + public init(notificationType: AccessibilityNotificationType, argument: Any?) { self.notificationType = notificationType - self.argumentHandler = argumentHandler - self.operationType = operationType + self.argument = argument } public override func main() { @@ -70,8 +62,7 @@ public class AccessbilityOperation: MVMCoreOperation { timerSource?.setEventHandler { Task { @MainActor [weak self] in if !(self?.isCancelled ?? false), let notification = self?.notificationType.accessibilityNotification { - print("argumentHandler \(self?.argumentHandler?(self?.operationType))") - UIAccessibility.post(notification: notification, argument: self?.argumentHandler?(self?.operationType)) + UIAccessibility.post(notification: notification, argument: self?.argument) self?.markAsFinished() } else { self?.stop() @@ -99,13 +90,13 @@ open class AccessibilityHandler { } public weak var delegate: MVMCoreViewControllerProtocol? public var previousAccessiblityElement: Any? + public var anyCancellable: Set = [] private var accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 return queue }() - private var anyCancellable: Set = [] private var accessibilityId: String? private var announcementText: String? private var hasTopNotitificationInPage: Bool = false @@ -127,7 +118,7 @@ open class AccessibilityHandler { } /// Registers to know when pages change. - private func registerForPageChanges() { + open func registerForPageChanges() { MVMCoreNavigationHandler.shared()?.addDelegate(self) } @@ -135,8 +126,6 @@ open class AccessibilityHandler { //Since foucs shifted to other elements cancelling existing focus shift notifications if any NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) .sink { [weak self] notification in - print("testing \(UIAccessibility.focusedElement(using: .notificationVoiceOver))") - print("testing \(notification.userInfo)") self?.cancelAllOperations() }.store(in: &anyCancellable) } @@ -145,7 +134,7 @@ open class AccessibilityHandler { NotificationHandler.shared()?.onNotificationWillShow .sink { [weak self] (_, model) in self?.hasTopNotitificationInPage = true - self?.capturePreviousFocusElement(for: model.molecule) + self?.capturePreviousFocusElement() }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationShown .sink { [weak self] (view, model) in @@ -157,15 +146,15 @@ open class AccessibilityHandler { }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationDismissed .sink { [weak self] (view, model) in - self?.postAccessbilityToPrevElement(for: model.molecule) + self?.postAccessbilityToPrevElement() }.store(in: &anyCancellable) } - open func capturePreviousFocusElement(for model: MoleculeModelProtocol) { + open func capturePreviousFocusElement() { previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) } - open func postAccessbilityToPrevElement(for model: MoleculeModelProtocol) { + open func postAccessbilityToPrevElement() { post(notification: .layoutChanged, argument: previousAccessiblityElement) } @@ -177,15 +166,9 @@ open class AccessibilityHandler { accessibilityOperationQueue.cancelAllOperations() } - open func post(webpageChanged type: AccessibilityNotificationType) { - post(notification: type) - } - - public func post(notification type: AccessibilityNotificationType, operationType: NavigationOperationType = .default, argument: Any? = nil) { + public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } - let accessbilityOperation = AccessbilityOperation(notificationType: type, operationType: operationType) { [weak self] in - ($0 == .tab) ? self?.getFirstFocusedElementOnScreen() : argument - } + let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) add(operation: accessbilityOperation) } @@ -204,8 +187,7 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { previousAccessiblityElement = nil delegate = viewController as? MVMCoreViewControllerProtocol if let announcementText { - let accessbilityOperation = AccessbilityOperation(notificationType: .announcement) { _ in announcementText } - add(operation: accessbilityOperation) + post(notification: .announcement, argument: announcementText) } } @@ -213,17 +195,13 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { open func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { guard UIAccessibility.isVoiceOverRunning, canPostAccessbilityNotification(for: viewController) else { return } - //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count - /*var navigationOperationType: NavigationType = .push - if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" { - navigationOperationType = .set - }*/ + //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count. If we have top notification in page on pageLoad, we have postnotification for shifting the focus so in this case we are not posting accessiblity notifcation if hasTopNotitificationInPage { previousAccessiblityElement = getFirstFocusedElementOnScreen() } else { let accessbilityElement = getAccessbilityFocusedElement() let operationType: NavigationOperationType = navigationController.children.count == 1 ? .tab : .default //TODO: - need to identify the operationType - post(notification: operationType == .tab ? .controllerChanged : .layoutChanged, operationType: operationType, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + post(notification: operationType == .tab ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) accessibilityId = nil } } From e61d7c5b076b25ff375d185cc4d5630ccc9cd7f2 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 26 Jul 2023 20:31:06 +0530 Subject: [PATCH 014/112] reverted changes in notification molecule model --- .../Molecules/TopNotification/NotificationMoleculeModel.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift index 9143e1c3..0f3e546a 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift @@ -35,20 +35,18 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol, Acc public var button: ButtonModel? public var closeButton: NotificationXButtonModel? public var style: NotificationMoleculeModel.Style = .success - public var id: String? //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil, id: String? = nil) { + public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil) { self.headline = headline self.style = style self.backgroundColor = backgroundColor self.body = body self.button = button self.closeButton = closeButton - self.id = id super.init() } From 21a339f0453753ff19149515c14a1fa3ee5b7634 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 26 Jul 2023 21:06:29 +0530 Subject: [PATCH 015/112] removed AccessibilityElementProtocol --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 1 - .../Molecules/TopNotification/NotificationMoleculeModel.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index cea6ffc5..b476324f 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -191,7 +191,6 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { } } - @MainActor open func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { guard UIAccessibility.isVoiceOverRunning, canPostAccessbilityNotification(for: viewController) else { return } diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift index 0f3e546a..d26bb06c 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift @@ -7,7 +7,7 @@ // -open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol, AccessibilityElementProtocol { +open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol { /** The style of the notification: From 44833e74201fa044cdbfb2e54087df94185c411f Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 27 Jul 2023 14:16:32 +0530 Subject: [PATCH 016/112] Added spanish content for hardcoded strings --- .../SupportingFiles/Strings/es.lproj/Localizable.strings | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index 0c441812..150ac167 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -60,7 +60,10 @@ // Carousel "MVMCoreUIPageControl_currentpage_index" = "página %@ de %d"; -"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %@ of %d"; +"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %@ of %d seleccionado"; +"MVMCoreUIPageControlslides_currentpage_index_accessibilityAnnouncement" = "diapositiva %@ of %d"; +"MVMCoreUIPageControlslides_totalslides" = "Carrusel contiene %d diapositivas,"; + //Styler "CountDownDay" = " día"; "CountDownHour" = " hora"; From f8bc4c11678123079476befa7c97208a984d8fe9 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 2 Aug 2023 17:32:37 +0530 Subject: [PATCH 017/112] added default id to UUID --- MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift | 7 ++++--- .../BaseClasses/Protocols/AccessibilityProtocol.swift | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 7336bbad..2c07134a 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -30,7 +30,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? - public var id: String? + public var id: String //-------------------------------------------------- // MARK: - Keys @@ -76,9 +76,10 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit // MARK: - Initializer //-------------------------------------------------- - public init(_ state: Bool) { + public init(_ state: Bool, id: String = UUID().uuidString) { self.selected = state baseValue = state + self.id = id } //-------------------------------------------------- @@ -126,7 +127,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit } enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false - id = try typeContainer.decodeIfPresent(String.self, forKey: .id) + id = try typeContainer.decode(forKey: .id, default: { UUID().uuidString }()) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift index d0501874..2a6456dd 100644 --- a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift +++ b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift @@ -15,5 +15,5 @@ import Foundation public protocol AccessibilityElementProtocol: Identifiable { - var id: String? { get set } + var id: String { get set } } From 5633d564a33646036669ee9d1cc338926ec8e45d Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 2 Aug 2023 22:12:40 +0530 Subject: [PATCH 018/112] Commented logic for focus stuck issue --- MVMCoreUI/Atomic/Organisms/Stack.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index 1f35e34d..524635ec 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -48,13 +48,14 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } isAccessibilityElement = false - var accessibleViews: [Any] = [] - - for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { - accessibleViews.append(view) - } - - accessibilityElements = accessibleViews + // TO DO need more investigation +// var accessibleViews: [Any] = [] +// +// for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { +// accessibleViews.append(view) +// } +// +// accessibilityElements = accessibleViews } /// Removes all stack items views from the view. From 94d6787d3b8138b6dc4d806d51d2d44ec1095de8 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 2 Aug 2023 22:39:17 +0530 Subject: [PATCH 019/112] refactoring --- MVMCoreUI/Atomic/Organisms/Stack.swift | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index 524635ec..a2a07953 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -48,14 +48,15 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } isAccessibilityElement = false - // TO DO need more investigation -// var accessibleViews: [Any] = [] -// -// for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { -// accessibleViews.append(view) -// } -// -// accessibilityElements = accessibleViews + // TODO: need more investigation + /* var accessibleViews: [Any] = [] + + for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { + accessibleViews.append(view) + } + + accessibilityElements = accessibleViews + */ } /// Removes all stack items views from the view. From 4c2214d7d4fa1774d687fb46d02d7808484354e5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 10:37:42 -0400 Subject: [PATCH 020/112] VDS Button to atomic beginning/ --- .../Atomic/Atoms/Buttons/ButtonModel.swift | 18 +- .../Atomic/Atoms/Buttons/PillButton.swift | 195 ++++++------------ .../Atomic/Extensions/VDS-Enums+Codable.swift | 2 + MVMCoreUI/Styles/Styler.swift | 36 ---- 4 files changed, 79 insertions(+), 172 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 08afefde..22ee9b8b 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -8,10 +8,10 @@ import UIKit import VDSColorTokens +import VDS public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?) - open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { //-------------------------------------------------- // MARK: - Properties @@ -26,13 +26,13 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var action: ActionModelProtocol public var enabled: Bool = true public var width: CGFloat? - public var style: Styler.Button.Style? { + public var style: Use? { didSet { guard let style = style else { return } setFacade(by: style) } } - public var size: Styler.Button.Size? = .standard + public var size: ButtonSize = .large public var groupName: String = "" public var inverted: Bool = false @@ -160,14 +160,14 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat disabledBorderColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) } - public func setFacade(by style: Styler.Button.Style) { - + public func setFacade(by style: VDS.Use) { switch style { case .primary: setPrimaryFacade() - case .secondary: setSecondaryFacade() + @unknown default: + setPrimaryFacade() } } @@ -211,17 +211,17 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat action = try typeContainer.decodeModel(codingKey: .action) ///Style captured from the JSON - if let style = try typeContainer.decodeIfPresent(Styler.Button.Style.self, forKey: .style){ + if let style = try typeContainer.decodeIfPresent(Use.self, forKey: .style) { self.style = style setFacade(by: style) - } else if let style = decoder.context?.value(forKey: CodingKeys.style.stringValue) as? Styler.Button.Style { ///Reading the style param from context which is set is molecules, ex: TwoButtonView + } else if let style = decoder.context?.value(forKey: CodingKeys.style.stringValue) as? Use { ///Reading the style param from context which is set is molecules, ex: TwoButtonView self.style = style setFacade(by: style) } else { ///Default style setFacade(by: .primary) } - if let size = try typeContainer.decodeIfPresent(Styler.Button.Size.self, forKey: .size) { + if let size = try typeContainer.decodeIfPresent(VDS.ButtonSize.self, forKey: .size) { self.size = size } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index e578fa51..56df2044 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -8,35 +8,19 @@ import UIKit import VDSColorTokens +import VDS -open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { +open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - /// Used to size the button. - var size = MVMCoreUIUtility.getWidth() - + var model: MoleculeModelProtocol? var buttonModel: ButtonModel? { get { model as? ButtonModel } } - /// Need to re-style on set. - open override var isEnabled: Bool { - didSet { style(with: buttonModel) } - } - - open var buttonSize: Styler.Button.Size = .standard { - didSet { buttonModel?.size = buttonSize } - } - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var widthConstraint: NSLayoutConstraint? - public var minimumWidthConstraint: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -44,127 +28,29 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { @objc public convenience init(asPrimaryButton isPrimary: Bool, makeTiny istiny: Bool) { let model = ButtonModel(with: "", action: ActionNoopModel()) model.style = isPrimary ? .primary : .secondary - model.size = istiny ? .tiny : .standard + model.size = istiny ? .small : .large self.init(model: model, nil, nil) } - //-------------------------------------------------- - // MARK: - Computed Properties - //-------------------------------------------------- - - public var enabledTitleColor: UIColor? { - get { titleColor(for: .normal) } - set { setTitleColor(newValue, for: .normal) } - } - - public var disabledTitleColor: UIColor? { - get { titleColor(for: .disabled) } - set { setTitleColor(newValue, for: .disabled) } - } - - public var borderColor: UIColor? { - get { - guard let currentColor = layer.borderColor else { return nil } - return UIColor(cgColor: currentColor) - } - set { layer.borderColor = newValue?.cgColor } - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - /// The primary styling for a button. Should be used for main buttons - public func stylePrimary() { - let buttonModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) - style(with: buttonModel) - } - - /// The secondary styling for a button. Should be used for secondary buttons - public func styleSecondary() { - let buttonModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) - style(with: buttonModel) - } - - /// Styles the button based on the model style - private func style(with model: ButtonModel?) { - - layer.borderWidth = model?.style == .secondary ? 1 : 0 - - if let titleColor = model?.enabledColors.text { - enabledTitleColor = titleColor - } - - if let disabledTitleColor = model?.disabledColors.text { - self.disabledTitleColor = disabledTitleColor - } - - #if DEBUG - // Useful to detect with isHittable when performing UI testing. - isAccessibilityElement = isEnabled - #endif - - if isEnabled { - if let fillColor = model?.enabledColors.fill { - backgroundColor = fillColor - } - - if let borderColor = model?.enabledColors.border { - self.borderColor = borderColor - } - } else { - if let fillColor = model?.disabledColors.fill { - backgroundColor = fillColor - } - - if let borderColor = model?.disabledColors.border { - self.borderColor = borderColor - } - } - } - - private func getInnerPadding() -> CGFloat { - buttonSize.getHeight() / 2.0 - } - - private func getContentEdgeInsets() -> UIEdgeInsets { - var verticalPadding = 0.0 - var horizontalPadding = 0.0 - switch buttonSize { - case .standard: - verticalPadding = Padding.Three - horizontalPadding = Padding.Five - break - case .small: - verticalPadding = Padding.Two - horizontalPadding = Padding.Four - break - case .tiny: - verticalPadding = Padding.One - horizontalPadding = Padding.Two - break - } - return UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) - } - //-------------------------------------------------- // MARK: - MVMCoreViewProtocol //-------------------------------------------------- - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - // The button will get styled in the enable check in super. - super.set(with: model, delegateObject, additionalData) - + open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let model = model as? ButtonModel else { return } + self.model = model + + if let accessibilityIdentifier = model.accessibilityIdentifier { + self.accessibilityIdentifier = accessibilityIdentifier + } setTitle(model.title, for: .normal) if let accessibilityText = model.accessibilityText { accessibilityLabel = accessibilityText } - if let size = model.size { - buttonSize = size - } + isEnabled = model.enabled + size = model.size model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { @@ -173,7 +59,62 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { } FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) + + guard let model = model as? ButtonModelProtocol else { return } + //set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } +} + +open func reset() { + backgroundColor = .clear +} + +// MARK: Overridables +// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead. +open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + model.moleculeName +} + +open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil } + +open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { nil } + +//-------------------------------------------------- +// MARK: - Accessibility +//-------------------------------------------------- + +open override func accessibilityActivate() -> Bool { + guard isEnabled else { return false } + buttonAction?(self) + return buttonAction != nil +} + +} + +// MARK: - MVMCoreViewProtocol +extension Button: MVMCoreViewProtocol { + +open func updateView(_ size: CGFloat) { } + +/// Will be called only once. +open func setupView() { + isAccessibilityElement = true + accessibilityTraits = .button + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + titleLabel?.numberOfLines = 0 + titleLabel?.lineBreakMode = .byWordWrapping +} +} + +// MARK: AppleGuidelinesProtocol +extension Button: AppleGuidelinesProtocol { + +override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + Self.acceptablyOutsideBounds(point: point, bounds: bounds) +} +} + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return (model as? ButtonModel)?.size?.getHeight() diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 6c143d9c..b2463ed1 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -16,3 +16,5 @@ extension Icon.Size: Codable {} extension TileContainer.BackgroundColor: Codable {} extension TileContainer.Padding: Codable {} extension TileContainer.AspectRatio: Codable {} +extension ButtonSize: Codable {} +extension Use: Codable {} diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index 2cbc9a00..2daac86a 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -159,42 +159,6 @@ open class Styler { } } - public enum Button { - - public enum Style: String, Codable { - case primary - case secondary - } - ///MVA 3.0 - Button sizes are standard(default size), small, Tiny. Tiny button has been depricated as of Rebranding 3.0. - public enum Size: String, Codable { - case standard - case small - case tiny - - func getHeight() -> CGFloat { - switch self { - case .standard: - return 44 - case .small: - return 32 - case .tiny: - return 20 - } - } - - func minimumWidth() -> CGFloat { - switch self { - case .standard: - return 76 - case .small: - return 0 - case .tiny: - return 49 - } - } - } - } - //-------------------------------------------------- // MARK: - Functions //-------------------------------------------------- From 8e35e8036a5bd5a2b5a4bc12c4714d4ecd0a8c41 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 13:55:17 -0400 Subject: [PATCH 021/112] Tiny button to small --- .../Atomic/Atoms/Buttons/ButtonModel.swift | 4 +- .../Atomic/Atoms/Buttons/PillButton.swift | 180 +++++++----------- .../Atomic/Extensions/VDS-Enums+Codable.swift | 21 +- .../ListDeviceComplexButtonMediumModel.swift | 2 +- .../ListDeviceComplexButtonSmallModel.swift | 2 +- ...htVariableButtonAllTextAndLinksModel.swift | 2 +- .../TwoButtonViewModel.swift | 6 +- .../NotificationMoleculeModel.swift | 2 +- .../BGImageHeadlineBodyButtonModel.swift | 2 +- .../HeadlineBodyButtonModel.swift | 2 +- 10 files changed, 105 insertions(+), 118 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 22ee9b8b..b15f8adb 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -32,7 +32,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat setFacade(by: style) } } - public var size: ButtonSize = .large + public var size: VDS.Button.Size = .large public var groupName: String = "" public var inverted: Bool = false @@ -221,7 +221,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat setFacade(by: .primary) } - if let size = try typeContainer.decodeIfPresent(VDS.ButtonSize.self, forKey: .size) { + if let size = try typeContainer.decodeIfPresent(VDS.Button.Size.self, forKey: .size) { self.size = size } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index 56df2044..4a812a57 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -9,8 +9,10 @@ import UIKit import VDSColorTokens import VDS +import MVMCore +import Combine -open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol { +open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol, MVMCoreViewProtocol, MFButtonProtocol { //-------------------------------------------------- // MARK: - Properties @@ -21,10 +23,33 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi get { model as? ButtonModel } } + var onClickCancellable: Cancellable? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + open override func setup() { + super.setup() + setupView() + } + + //-------------------------------------------------- + // MARK: - Convenience + //-------------------------------------------------- + @objc public convenience init(asPrimaryButton isPrimary: Bool, makeTiny istiny: Bool) { let model = ButtonModel(with: "", action: ActionNoopModel()) model.style = isPrimary ? .primary : .secondary @@ -32,8 +57,20 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi self.init(model: model, nil, nil) } + /// The primary styling for a button. Should be used for main buttons + public func stylePrimary() { + let buttonModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) + use = .primary + } + + /// The secondary styling for a button. Should be used for secondary buttons + public func styleSecondary() { + let buttonModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) + use = .secondary + } + //-------------------------------------------------- - // MARK: - MVMCoreViewProtocol + // MARK: - MoleculeViewProtocol //-------------------------------------------------- open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { @@ -51,129 +88,60 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi isEnabled = model.enabled size = model.size +// if let backgroundColor = model.backgroundColor { +// self.backgroundColor = backgroundColor.uiColor +// } model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { - self?.enableField(model.enabled) + self?.isEnabled = model.enabled }) } FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - guard let model = model as? ButtonModelProtocol else { return } - //set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) - } -} - -open func reset() { - backgroundColor = .clear -} - -// MARK: Overridables -// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead. -open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - model.moleculeName -} - -open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil } - -open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { nil } - -//-------------------------------------------------- -// MARK: - Accessibility -//-------------------------------------------------- - -open override func accessibilityActivate() -> Bool { - guard isEnabled else { return false } - buttonAction?(self) - return buttonAction != nil -} - -} - -// MARK: - MVMCoreViewProtocol -extension Button: MVMCoreViewProtocol { - -open func updateView(_ size: CGFloat) { } - -/// Will be called only once. -open func setupView() { - isAccessibilityElement = true - accessibilityTraits = .button - translatesAutoresizingMaskIntoConstraints = false - insetsLayoutMarginsFromSafeArea = false - titleLabel?.numberOfLines = 0 - titleLabel?.lineBreakMode = .byWordWrapping -} -} - -// MARK: AppleGuidelinesProtocol -extension Button: AppleGuidelinesProtocol { - -override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - Self.acceptablyOutsideBounds(point: point, bounds: bounds) -} -} - - - open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - return (model as? ButtonModel)?.size?.getHeight() + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } - open override func updateView(_ size: CGFloat) { - super.updateView(size) - self.size = size - - switch buttonSize { - case .tiny: - titleLabel?.font = Styler.Font.BoldMicro.getFont(false) - case .small: - titleLabel?.font = Styler.Font.BoldBodySmall.getFont(false) - case .standard: - titleLabel?.font = Styler.Font.BoldBodyLarge.getFont(false) - } - - layer.cornerRadius = getInnerPadding() - contentEdgeInsets = getContentEdgeInsets() - - if let contraint = buttonModel?.width { - - if widthConstraint == nil { - widthConstraint = widthAnchor.constraint(equalToConstant: contraint) - } else if widthConstraint?.constant != contraint { - widthConstraint?.constant = contraint - } - widthConstraint?.isActive = true - minimumWidthConstraint?.isActive = false - } else { - - if minimumWidthConstraint == nil { - minimumWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: buttonSize.minimumWidth()) - } else { - minimumWidthConstraint?.constant = buttonSize.minimumWidth() - } - minimumWidthConstraint?.isActive = true - widthConstraint?.isActive = false - } + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + + open override func reset() { + super.reset() + //backgroundColor = .clear } - open override func setupView() { - super.setupView() - - titleLabel?.numberOfLines = 1 - titleLabel?.lineBreakMode = .byTruncatingTail - titleLabel?.textAlignment = .center - contentHorizontalAlignment = .center - stylePrimary() + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return (model as? ButtonModel)?.size.height } + open func updateView(_ size: CGFloat) {} + + open func setupView() {} + //-------------------------------------------------- // MARK: - MVMCoreUIViewConstrainingProtocol //-------------------------------------------------- open func horizontalAlignment() -> UIStackView.Alignment { .center } - public func enableField(_ enable: Bool) { - isEnabled = enable + //-------------------------------------------------- + // MARK: - Action + //-------------------------------------------------- + + open func set(with actionModel: ActionModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + onClickCancellable = onClickSubscriber.publisher.sink { [weak self] _ in + guard let self = self, + let actionModel = self.buttonModel?.action else { return } + Task(priority: .userInitiated) { + try await Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.model) + } + } + } + + open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, sourceModel: MoleculeModelProtocol? = nil) async throws { + guard delegateObject?.buttonDelegate?.button?(button, shouldPerformActionWithMap: model.toJSON(), additionalData: additionalData) ?? true else { return } + try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: model, additionalData: MVMCoreUIActionHandler.add(sourceModel: sourceModel, to: additionalData), delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index b2463ed1..ea462930 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -16,5 +16,24 @@ extension Icon.Size: Codable {} extension TileContainer.BackgroundColor: Codable {} extension TileContainer.Padding: Codable {} extension TileContainer.AspectRatio: Codable {} -extension ButtonSize: Codable {} extension Use: Codable {} +extension VDS.Button.Size: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let string = try container.decode(String.self) + switch string { + case VDS.Button.Size.large.rawValue: + self = .large + case VDS.Button.Size.small.rawValue, "tiny": + self = .small + default: + self = .large + } + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift index 88f7fe97..54b313e6 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift @@ -42,7 +42,7 @@ public class ListDeviceComplexButtonMediumModel: ListItemModel, MoleculeModelPro override public func setDefaults() { super.setDefaults() - button.size = .tiny + button.size = .small button.style = .secondary } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmallModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmallModel.swift index 13be8c03..d06a3845 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmallModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmallModel.swift @@ -42,7 +42,7 @@ public class ListDeviceComplexButtonSmallModel: ListItemModel, MoleculeModelProt override public func setDefaults() { super.setDefaults() - button.size = .tiny + button.size = .small button.style = .secondary } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift index 21a4b08c..30856e55 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift @@ -32,7 +32,7 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul override public func setDefaults() { super.setDefaults() - self.button.size = .tiny + self.button.size = .small self.button.style = .secondary } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index b5d07c4a..a7b4055c 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDS public class TwoButtonViewModel: ParentMoleculeModelProtocol { //-------------------------------------------------- @@ -56,13 +56,13 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol { //set context value for 'primary' style to be set for the primaryButton in case the //property is not returned in the JSON and once decoded, this value is removed from the context - try decoder.setContext(value: Styler.Button.Style.primary, for: "style") { + try decoder.setContext(value: Use.primary, for: "style") { self.primaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .primaryButton) } //set context value for 'secondary' style to be set for the primaryButton in case the //property is not returned in the JSON and once decoded, this value is removed from the context - try decoder.setContext(value: Styler.Button.Style.secondary, for: "style") { + try decoder.setContext(value: Use.secondary, for: "style") { self.secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton) } } diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift index d26bb06c..27ca95a8 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift @@ -89,7 +89,7 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol { } } - button?.size = .tiny + button?.size = .small button?.style = .secondary switch style { case .error, .warning: diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift index 99ee1567..4e2d031a 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift @@ -54,7 +54,7 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc image.height = BGImageHeadlineBodyButton.heightConstant } - button?.size = .tiny + button?.size = .small button?.style = .secondary } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift index 1b713720..347277fd 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift @@ -38,7 +38,7 @@ public class HeadlineBodyButtonModel: MoleculeModelProtocol { /// Defaults to set public func setDefaults() { - button.size = .tiny + button.size = .small button.style = .secondary } From 741bf0501b2f739d8e3bdae9eb5ce6da498480fe Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 14:14:30 -0400 Subject: [PATCH 022/112] Updates to Button --- .../Atomic/Atoms/Buttons/PillButton.swift | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index 4a812a57..e8105d3e 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -18,12 +18,12 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi // MARK: - Properties //-------------------------------------------------- - var model: MoleculeModelProtocol? - var buttonModel: ButtonModel? { + open var model: MoleculeModelProtocol? + public var buttonModel: ButtonModel? { get { model as? ButtonModel } } - var onClickCancellable: Cancellable? + internal var onClickCancellable: Cancellable? //-------------------------------------------------- // MARK: - Initializers @@ -58,14 +58,14 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi } /// The primary styling for a button. Should be used for main buttons - public func stylePrimary() { - let buttonModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) + open func stylePrimary() { + model = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) use = .primary } /// The secondary styling for a button. Should be used for secondary buttons - public func styleSecondary() { - let buttonModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) + open func styleSecondary() { + model = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) use = .secondary } @@ -81,26 +81,25 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi self.accessibilityIdentifier = accessibilityIdentifier } - setTitle(model.title, for: .normal) - if let accessibilityText = model.accessibilityText { - accessibilityLabel = accessibilityText - } - + text = model.title isEnabled = model.enabled size = model.size // if let backgroundColor = model.backgroundColor { // self.backgroundColor = backgroundColor.uiColor // } + if let accessibilityText = model.accessibilityText { + accessibilityLabel = accessibilityText + } + + FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) + + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { self?.isEnabled = model.enabled }) } - - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - - set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } //-------------------------------------------------- From c82a0cd4d60d2ab47aaf2049267d16b22662f841 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 14:49:07 -0400 Subject: [PATCH 023/112] Make PillButton VDSMoleculeViewProtocol --- .../Atomic/Atoms/Buttons/PillButton.swift | 89 +++++++------------ .../Atomic/Extensions/VDS-Enums+Codable.swift | 2 +- 2 files changed, 32 insertions(+), 59 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index e8105d3e..2c1c44e7 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -12,40 +12,18 @@ import VDS import MVMCore import Combine -open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol, MVMCoreViewProtocol, MFButtonProtocol { - +open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol, MVMCoreViewProtocol, MFButtonProtocol, VDSMoleculeViewProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var model: MoleculeModelProtocol? - public var buttonModel: ButtonModel? { - get { model as? ButtonModel } - } - + public var viewModel: ButtonModel! + public var delegateObject: MVMCoreUIDelegateObject? + public var additionalData: [AnyHashable: Any]? + internal var onClickCancellable: Cancellable? - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - - required public init() { - super.init(frame: .zero) - } - - public override init(frame: CGRect) { - super.init(frame: .zero) - } - - public required init?(coder: NSCoder) { - super.init(coder: coder) - } - - open override func setup() { - super.setup() - setupView() - } - //-------------------------------------------------- // MARK: - Convenience //-------------------------------------------------- @@ -59,45 +37,45 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi /// The primary styling for a button. Should be used for main buttons open func stylePrimary() { - model = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) + viewModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) use = .primary } /// The secondary styling for a button. Should be used for secondary buttons open func styleSecondary() { - model = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) + viewModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) use = .secondary } + open override func setup() { + super.setup() + setupView() + } + //-------------------------------------------------- - // MARK: - MoleculeViewProtocol + // MARK: - VDSMoleculeViewProtocol //-------------------------------------------------- - open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let model = model as? ButtonModel else { return } - self.model = model - - if let accessibilityIdentifier = model.accessibilityIdentifier { + public func viewModelDidUpdate() { + if let accessibilityIdentifier = viewModel.accessibilityIdentifier { self.accessibilityIdentifier = accessibilityIdentifier } - text = model.title - isEnabled = model.enabled - size = model.size -// if let backgroundColor = model.backgroundColor { -// self.backgroundColor = backgroundColor.uiColor -// } - if let accessibilityText = model.accessibilityText { + text = viewModel.title + isEnabled = viewModel.enabled + size = viewModel.size + use = viewModel.style ?? .primary + if let accessibilityText = viewModel.accessibilityText { accessibilityLabel = accessibilityText } - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) + set(with: viewModel.action, delegateObject: delegateObject, additionalData: additionalData) - model.updateUI = { [weak self] in + viewModel.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { - self?.isEnabled = model.enabled + self?.viewModelDidUpdate() }) } } @@ -106,11 +84,6 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi // MARK: - MVMCoreViewProtocol //-------------------------------------------------- - open override func reset() { - super.reset() - //backgroundColor = .clear - } - open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return (model as? ButtonModel)?.size.height } @@ -130,13 +103,13 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi //-------------------------------------------------- open func set(with actionModel: ActionModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - onClickCancellable = onClickSubscriber.publisher.sink { [weak self] _ in - guard let self = self, - let actionModel = self.buttonModel?.action else { return } - Task(priority: .userInitiated) { - try await Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.model) + onClickSubscriber = publisher(for: .touchUpInside) + .sink {[weak self] control in + guard let self = self else { return } + Task(priority: .userInitiated) { + try await Self.performButtonAction(with: self.viewModel.action, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.viewModel) + } } - } } open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, sourceModel: MoleculeModelProtocol? = nil) async throws { diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index ea462930..369dbb18 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -22,7 +22,7 @@ extension VDS.Button.Size: Codable { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) switch string { - case VDS.Button.Size.large.rawValue: + case VDS.Button.Size.large.rawValue, "standard": self = .large case VDS.Button.Size.small.rawValue, "tiny": self = .small From a925560ecbb463bad2eabb00a88a540ca7455368 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 16:18:10 -0400 Subject: [PATCH 024/112] RawRepresentableCodable --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++ .../Atomic/Atoms/Buttons/PillButton.swift | 9 +++-- .../Atomic/Extensions/VDS-Enums+Codable.swift | 22 ++-------- .../Utility/RawRepresentableCodable.swift | 40 +++++++++++++++++++ 4 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 MVMCoreUI/Utility/RawRepresentableCodable.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index acb13d9a..964bc30c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -289,6 +289,7 @@ AF766D262A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF766D252A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift */; }; AF7E509829E477C1009DC2AD /* AlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509629E477C0009DC2AD /* AlertHandler.swift */; }; AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509729E477C0009DC2AD /* AlertController.swift */; }; + AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF81182F2AB39B0900FAD1BA /* RawRepresentableCodable.swift */; }; AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931F29E5CA73001A9663 /* AlertOperation.swift */; }; AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */; }; AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */; }; @@ -875,6 +876,7 @@ AF766D252A3CD4C600749099 /* UIAccessibilityTraits+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAccessibilityTraits+Codable.swift"; sourceTree = ""; }; AF7E509629E477C0009DC2AD /* AlertHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertHandler.swift; sourceTree = ""; }; AF7E509729E477C0009DC2AD /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; + AF81182F2AB39B0900FAD1BA /* RawRepresentableCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentableCodable.swift; sourceTree = ""; }; AFA4931F29E5CA73001A9663 /* AlertOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = ""; }; AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = ""; }; AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = ""; }; @@ -2152,6 +2154,7 @@ D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */, 0AF60F0826B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift */, + AF81182F2AB39B0900FAD1BA /* RawRepresentableCodable.swift */, ); path = Utility; sourceTree = ""; @@ -2746,6 +2749,7 @@ 0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */, 012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */, D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */, + AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */, 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index 2c1c44e7..d99b78f4 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -68,16 +68,17 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi if let accessibilityText = viewModel.accessibilityText { accessibilityLabel = accessibilityText } - - FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - + set(with: viewModel.action, delegateObject: delegateObject, additionalData: additionalData) viewModel.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { - self?.viewModelDidUpdate() + guard let self = self else { return } + self.isEnabled = self.viewModel.enabled }) } + + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 369dbb18..ee504e2f 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -17,23 +17,7 @@ extension TileContainer.BackgroundColor: Codable {} extension TileContainer.Padding: Codable {} extension TileContainer.AspectRatio: Codable {} extension Use: Codable {} -extension VDS.Button.Size: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let string = try container.decode(String.self) - switch string { - case VDS.Button.Size.large.rawValue, "standard": - self = .large - case VDS.Button.Size.small.rawValue, "tiny": - self = .small - default: - self = .large - } - - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self) - } +extension VDS.Button.Size: RawRepresentableCodable { + public static var mapping: [String : VDS.Button.Size] { ["standard": .large, "tiny": .small] } + public static var defaultValue: VDS.Button.Size? { nil } } diff --git a/MVMCoreUI/Utility/RawRepresentableCodable.swift b/MVMCoreUI/Utility/RawRepresentableCodable.swift new file mode 100644 index 00000000..09441add --- /dev/null +++ b/MVMCoreUI/Utility/RawRepresentableCodable.swift @@ -0,0 +1,40 @@ +// +// RawRepresentableCodable.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/14/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol RawRepresentableCodable: RawRepresentable, Codable where RawValue: Hashable & Decodable { + static var mapping: [RawValue: Self] { get } + static var defaultValue: Self? { get } +} + +public enum RawRepresentableCodableError: Swift.Error { + case invalid(value: String) +} + +extension RawRepresentableCodable { + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(RawValue.self) + if let found = Self(rawValue: rawValue) { + self = found + } else if let found = Self.mapping[rawValue] { + self = found + } else if let defaultValue = Self.defaultValue { + self = defaultValue + } else { + throw RawRepresentableCodableError.invalid(value: "\(rawValue)") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self) + } +} From c2cc637c382f1bed8997c8e465aeebe21dab00c2 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 14 Sep 2023 16:35:44 -0400 Subject: [PATCH 025/112] remove old junk --- .../Atomic/Atoms/Buttons/PillButton.swift | 33 +++---------------- .../TwoButtonView.swift | 7 ++-- .../NotificationMoleculeView.swift | 2 +- .../HeadlineBodyButton.swift | 2 +- 4 files changed, 10 insertions(+), 34 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index d99b78f4..ddffb179 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -24,34 +24,6 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi internal var onClickCancellable: Cancellable? - //-------------------------------------------------- - // MARK: - Convenience - //-------------------------------------------------- - - @objc public convenience init(asPrimaryButton isPrimary: Bool, makeTiny istiny: Bool) { - let model = ButtonModel(with: "", action: ActionNoopModel()) - model.style = isPrimary ? .primary : .secondary - model.size = istiny ? .small : .large - self.init(model: model, nil, nil) - } - - /// The primary styling for a button. Should be used for main buttons - open func stylePrimary() { - viewModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) - use = .primary - } - - /// The secondary styling for a button. Should be used for secondary buttons - open func styleSecondary() { - viewModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) - use = .secondary - } - - open override func setup() { - super.setup() - setupView() - } - //-------------------------------------------------- // MARK: - VDSMoleculeViewProtocol //-------------------------------------------------- @@ -91,6 +63,11 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi open func updateView(_ size: CGFloat) {} + open override func setup() { + super.setup() + setupView() + } + open func setupView() {} //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 5f846730..9a70c12e 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDS @objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- @@ -29,9 +29,8 @@ import UIKit //-------------------------------------------------- public func setDefaultAppearance() { - - primaryButton.stylePrimary() - secondaryButton.styleSecondary() + primaryButton.use = .primary + secondaryButton.use = .secondary } open override func updateView(_ size: CGFloat) { diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeView.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeView.swift index c731f6d6..200dea16 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeView.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeView.swift @@ -15,7 +15,7 @@ import Foundation public let headline = Label(fontStyle: .BoldBodySmall) public let body = Label(fontStyle: .RegularBodySmall) - public let button = PillButton(asPrimaryButton: false, makeTiny: true) + public let button = PillButton() public let closeButton = NotificationXButton() public var labelStack: Stack! public var horizontalStack: Stack! diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift index a40066e2..d7fced36 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift @@ -64,7 +64,7 @@ headlineBody.headlineLabel.font = Styler.Font.BoldTitleMedium.getFont() headlineBody.messageLabel.font = Styler.Font.RegularMicro.getFont() - button.styleSecondary() + button.use = .secondary button.isHidden = false buttonHeadlinePadding = PaddingTwo } From 34d3386d36b240a7ff3a0399c32e1f4a73477d97 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 14 Sep 2023 16:02:15 -0500 Subject: [PATCH 026/112] updated onClick Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift b/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift index 41d68493..32c8015a 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift @@ -50,14 +50,13 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{ //setup action if let action = viewModel.action { //add the subscriber - onClickSubscriber = publisher(for: .touchUpInside) - .sink {[weak self] control in - guard let self else { return } - MVMCoreUIActionHandler.performActionUnstructured(with: action, - sourceModel: self.viewModel, - additionalData: self.additionalData, - delegateObject: self.delegateObject) - } + 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) + } } } From fe580d12e4cca358ef6a0c9e4db19b8e0c961839 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 14 Sep 2023 16:02:26 -0500 Subject: [PATCH 027/112] updated onClick Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index ddffb179..c93a22db 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -12,7 +12,7 @@ import VDS import MVMCore import Combine -open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeViewProtocol, MVMCoreViewProtocol, MFButtonProtocol, VDSMoleculeViewProtocol { +open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, VDSMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties @@ -81,13 +81,12 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MoleculeVi //-------------------------------------------------- open func set(with actionModel: ActionModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - onClickSubscriber = publisher(for: .touchUpInside) - .sink {[weak self] control in - guard let self = self else { return } - Task(priority: .userInitiated) { - try await Self.performButtonAction(with: self.viewModel.action, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.viewModel) - } + onClick = { [weak self] control in + guard let self = self, let viewModel = self.viewModel else { return } + Task(priority: .userInitiated) { + try await Self.performButtonAction(with: viewModel.action, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: viewModel) } + } } open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, sourceModel: MoleculeModelProtocol? = nil) async throws { From 939ee1ec7abba49a8425a57f88fb4e611218e3be Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Fri, 15 Sep 2023 15:44:42 +0530 Subject: [PATCH 028/112] Adding option to show a label to loading overlay --- .../BaseControllers/MFLoadingViewController.m | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/BaseControllers/MFLoadingViewController.m b/MVMCoreUI/BaseControllers/MFLoadingViewController.m index 56ad6d3b..567790aa 100644 --- a/MVMCoreUI/BaseControllers/MFLoadingViewController.m +++ b/MVMCoreUI/BaseControllers/MFLoadingViewController.m @@ -12,11 +12,13 @@ #import "UIColor+MFConvenience.h" #import "MFStyler.h" #import "MVMCoreUICommonViewsUtility.h" +#import @interface MFLoadingViewController () @property (nullable, weak, nonatomic) MFLoadingSpinner *activityIndicator; @property (nullable, weak, nonatomic) UIView *transparentBackgroundView; +@property (nullable, weak, nonatomic) Label *indicatorText; @end @@ -27,23 +29,40 @@ view.backgroundColor = [UIColor clearColor]; self.view = view; + UIStackView *loadingStack = [[UIStackView alloc] initWithFrame:CGRectZero]; + loadingStack.axis = UILayoutConstraintAxisVertical; + loadingStack.alignment = UIStackViewAlignmentCenter; + loadingStack.spacing = 20; + // Sets up the loading view. MFLoadingSpinner *activityIndicatorView = [[MFLoadingSpinner alloc] initWithFrame:CGRectMake(0, 0, 36, 36)]; activityIndicatorView.backgroundColor = [UIColor clearColor]; activityIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:activityIndicatorView]; self.activityIndicator = activityIndicatorView; self.activityIndicator.accessibilityIdentifier = @"Loader"; [activityIndicatorView pinWidthAndHeight]; + Label *infoLabel = [Label commonLabelH3:YES]; + infoLabel.textAlignment = NSTextAlignmentCenter; + infoLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.indicatorText = infoLabel; + + [loadingStack addArrangedSubview:activityIndicatorView]; + [loadingStack addArrangedSubview:infoLabel]; + + loadingStack.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:loadingStack]; // Sets the constraints for the activityIndicatorView - [NSLayoutConstraint constraintPinSubview:activityIndicatorView pinCenterX:YES pinCenterY:YES]; + + [NSLayoutConstraint constraintWithItem:infoLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:1.0]; + [NSLayoutConstraint constraintPinSubview:loadingStack pinCenterX:YES pinCenterY:YES]; + [NSLayoutConstraint constraintPinSubview:loadingStack pinTop:NO topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; // Sets up the transparent background view. UIView *transparentBackground = [MVMCoreUICommonViewsUtility commonView]; transparentBackground.backgroundColor = [UIColor mfBackgroundGray]; transparentBackground.alpha = 0.9; - [view insertSubview:transparentBackground belowSubview:activityIndicatorView]; + [view insertSubview:transparentBackground belowSubview:loadingStack]; self.transparentBackgroundView = transparentBackground; // Sets the constraints of the transparent background view to be the same as the activity indicator view. @@ -61,6 +80,14 @@ [self.activityIndicator resumeSpinner]; } +- (void)startLoadingWith:(nullable NSString *) text{ + if([text length] != 0){ + self.indicatorText.text = text; + self.indicatorText.accessibilityLabel = text; + } + [self.activityIndicator resumeSpinner]; +} + - (void)stopLoading { [self.activityIndicator pauseSpinner]; } From 917a0f84806045af86189ba07be917146cb68a35 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 15 Sep 2023 12:13:02 -0400 Subject: [PATCH 029/112] PillButton inverted-> surface --- MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift | 1 + MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index b15f8adb..94a7778b 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -9,6 +9,7 @@ import UIKit import VDSColorTokens import VDS +import MVMCore public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index c93a22db..645c6602 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -37,6 +37,7 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonPr isEnabled = viewModel.enabled size = viewModel.size use = viewModel.style ?? .primary + surface = viewModel.inverted ? .dark : .light if let accessibilityText = viewModel.accessibilityText { accessibilityLabel = accessibilityText } From 68ad6595595294b562bc993be60b26570fc44cde Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 15 Sep 2023 12:13:44 -0400 Subject: [PATCH 030/112] pill button open --- MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index c93a22db..0bfad746 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -18,7 +18,7 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonPr // MARK: - Properties //-------------------------------------------------- - public var viewModel: ButtonModel! + open var viewModel: ButtonModel! public var delegateObject: MVMCoreUIDelegateObject? public var additionalData: [AnyHashable: Any]? @@ -28,7 +28,7 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonPr // MARK: - VDSMoleculeViewProtocol //-------------------------------------------------- - public func viewModelDidUpdate() { + open func viewModelDidUpdate() { if let accessibilityIdentifier = viewModel.accessibilityIdentifier { self.accessibilityIdentifier = accessibilityIdentifier } From 05069e044cb64ed9c850aeb17874a2d05f68092b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 09:55:56 -0500 Subject: [PATCH 031/112] added helper protocol Signed-off-by: Matt Bruce --- MVMCoreUI.xcodeproj/project.pbxproj | 6 +++++- .../Atomic/Extensions/VDS-Enums+Codable.swift | 2 ++ .../Atomic/Protocols/VDS-Interpreters.swift | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 MVMCoreUI/Atomic/Protocols/VDS-Interpreters.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index acb13d9a..a46d3b1a 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -587,6 +587,7 @@ EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */; }; 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 */; }; FD99130028E21E4900542CC3 /* RuleNotEqualsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */; }; /* End PBXBuildFile section */ @@ -1174,6 +1175,7 @@ EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyModelProtocol.swift; sourceTree = ""; }; EABFC1402763BB8D00E78B40 /* FormLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabel.swift; sourceTree = ""; }; EABFC151276913E800E78B40 /* FormLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabelModel.swift; sourceTree = ""; }; + EACCF38B2ABB346700E0F104 /* VDS-Interpreters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VDS-Interpreters.swift"; sourceTree = ""; }; FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleNotEqualsModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2415,6 +2417,7 @@ 011B58EE23A2AA850085F53C /* ModelProtocols */, 27559EFB27D691D3000836C1 /* ViewMaskingProtocol.swift */, EAA7801F290081320057DFDF /* VDSMoleculeViewProtocol.swift */, + EACCF38B2ABB346700E0F104 /* VDS-Interpreters.swift */, ); path = Protocols; sourceTree = ""; @@ -2972,6 +2975,7 @@ D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */, D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */, D2A92882241AAB67004E01C6 /* ScrollingViewController.swift in Sources */, + EACCF38C2ABB346700E0F104 /* VDS-Interpreters.swift in Sources */, C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */, 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */, D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 6c143d9c..3f132670 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -16,3 +16,5 @@ extension Icon.Size: Codable {} extension TileContainer.BackgroundColor: Codable {} extension TileContainer.Padding: Codable {} extension TileContainer.AspectRatio: Codable {} +extension VDS.Line.Style: Codable {} +extension VDS.Line.Orientation: Codable {} diff --git a/MVMCoreUI/Atomic/Protocols/VDS-Interpreters.swift b/MVMCoreUI/Atomic/Protocols/VDS-Interpreters.swift new file mode 100644 index 00000000..24237527 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/VDS-Interpreters.swift @@ -0,0 +1,18 @@ +// +// VDS-Interpreters.swift +// MVMCoreUI +// +// Created by Matt Bruce on 9/20/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +public protocol Invertable { + var inverted: Bool { get set } +} + +extension Invertable { + public var surface: Surface { return inverted ? .dark : .light } +} From 470bb160d6f928337d4497bb2f96f1034a3b700f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 13:25:33 -0500 Subject: [PATCH 032/112] update for VDS Protocol Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Protocols/VDSMoleculeViewProtocol.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Protocols/VDSMoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/VDSMoleculeViewProtocol.swift index 41349f2c..fcbd07d1 100644 --- a/MVMCoreUI/Atomic/Protocols/VDSMoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/VDSMoleculeViewProtocol.swift @@ -28,5 +28,9 @@ extension VDSMoleculeViewProtocol { viewModel = castedModel viewModelDidUpdate() } + + public func update(viewModel: ViewModel){ + set(with: viewModel, delegateObject, additionalData) + } } From 099fce290e81b8c2419529244d3e7ff9e10b911b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 13:26:31 -0500 Subject: [PATCH 033/112] updated LineModel and Line Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 125 +++++++++---------- MVMCoreUI/Atomic/Atoms/Views/LineModel.swift | 87 +++---------- 2 files changed, 73 insertions(+), 139 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index e36c8d23..c527a362 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -7,116 +7,107 @@ // import UIKit +import VDS - -@objcMembers open class Line: View { +@objcMembers open class Line: VDS.View, VDSMoleculeViewProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - var lineModel: LineModel? { - get { return model as? LineModel } - } - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var heightConstraint: NSLayoutConstraint? - public var widthConstraint: NSLayoutConstraint? - - open func updateLineConstraints(constant: CGFloat) { - if let useVerticalLine = lineModel?.useVerticalLine, useVerticalLine { - heightConstraint?.isActive = false - widthConstraint?.isActive = true - widthConstraint?.constant = constant - } else { - widthConstraint?.isActive = false - heightConstraint?.isActive = true - heightConstraint?.constant = constant + open var line = VDS.Line() + open var viewModel: LineModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + open var orientation: VDS.Line.Orientation = .horizontal { + didSet { + viewModel.orientation = orientation + update(viewModel: viewModel) } } //-------------------------------------------------- // MARK: - Initializer - //-------------------------------------------------- - + //-------------------------------------------------- public convenience init(pinTo view: UIView, edge: UIRectEdge, useMargin: Bool) { self.init(frame: .zero) addLine(to: view, edge: edge, useMargin: useMargin) } - public init() { - super.init(frame: .zero) - model = LineModel(type: .secondary) - setStyle(.secondary) + public required init() { + super.init() + viewModel = LineModel(type: .primary) } public override init(frame: CGRect) { super.init(frame: frame) - model = LineModel(type: .secondary) - setStyle(.secondary) + viewModel = LineModel(type: .primary) } public required init?(coder: NSCoder) { super.init(coder: coder) - model = LineModel(type: .secondary) - setStyle(.secondary) + viewModel = LineModel(type: .primary) } - public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - super.init(model: model, delegateObject, additionalData) + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func setup() { + super.setup() + addSubview(line) + line.pinToSuperView() } - + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - open func setStyle(_ style: LineModel.Style) { - lineModel?.type = style - backgroundColor = lineModel?.backgroundColor?.uiColor - updateLineConstraints(constant: lineModel?.thickness ?? 1) - } - open func shouldBeVisible() -> Bool { - guard let type = lineModel?.type else { return false } + guard let type = viewModel?.type else { return false } return type != .none } - //-------------------------------------------------- - // MARK: - MoleculeViewProtocol - //-------------------------------------------------- + open func setStyle(_ style: LineModel.Style) { + viewModel.type = style + update(viewModel: viewModel) + } open func addLine(to view: UIView, edge: UIRectEdge, useMargin: Bool) { view.addSubview(self) - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: self, useMargins: useMargin, pinTop: edge != .bottom, pinBottom: edge != .top, pinLeft: edge != .right, pinRight: edge != .left).values)) + NSLayoutConstraint.activate( + Array( + NSLayoutConstraint.pinView(toSuperview: self, + useMargins: useMargin, + pinTop: edge != .bottom, + pinBottom: edge != .top, + pinLeft: edge != .right, + pinRight: edge != .left).values + ) + ) } - open override func setupView() { - super.setupView() - heightConstraint = heightAnchor.constraint(equalToConstant: 1) - heightConstraint?.isActive = true - widthConstraint = widthAnchor.constraint(equalToConstant: 1) - widthConstraint?.isActive = false + //-------------------------------------------------- + // MARK: - VDSMoleculeViewProtocol + //-------------------------------------------------- + open func viewModelDidUpdate() { + surface = viewModel.surface + line.style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary + line.orientation = viewModel.orientation } - - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - if let lineModel = model as? LineModel { - setStyle(lineModel.type) - } + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + public func updateView(_ size: CGFloat) { + setNeedsDisplay() } - - open override func reset() { - setStyle(.secondary) - } - - public override static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - return (model as? LineModel)?.thickness ?? 1 + + public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 1 } } +//-------------------------------------------------- +// MARK: - MVMCoreUIViewConstrainingProtocol +//-------------------------------------------------- extension Line: MVMCoreUIViewConstrainingProtocol { open func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index 3b4c57e9..ecb39df0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -8,8 +8,9 @@ import UIKit import VDSColorTokens +import VDS -@objcMembers public class LineModel: MoleculeModelProtocol { +@objcMembers public class LineModel: MoleculeModelProtocol, Invertable { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- @@ -28,23 +29,9 @@ import VDSColorTokens case between } - /** - The style of the line: - - secondary (1 height, silver) - - primary (1 height, black) - - standard (1 height, silver) - deprecated - - thin (1 height, black) - deprecated - - medium (2 height, black) - - heavy (4 height, black) - - none (hidden) - */ public enum Style: String, Codable { case secondary case primary - case standard - case thin - case medium - case heavy case none } @@ -54,53 +41,14 @@ import VDSColorTokens public static var identifier: String = "line" public var id: String = UUID().uuidString - - public var type: Style = .secondary + public var backgroundColor: Color? + + public var type: Style = .primary public var frequency: Frequency? = .allExceptTop - - //TODO: use color insted of backgroundColor. Needs server changes - // public var color: Color? - private var _backgroundColor: Color? - public var backgroundColor: Color? { - get { - if let backgroundColor = _backgroundColor { return backgroundColor } - if inverted { - if type == .secondary || type == .standard { return Color(uiColor: VDSColor.paletteGray20) } - return Color(uiColor: VDSColor.elementsPrimaryOndark) - } - if type == .secondary || type == .standard { return Color(uiColor: VDSColor.paletteGray85) } - return Color(uiColor: VDSColor.elementsPrimaryOnlight) - } - set { - _backgroundColor = newValue - } - } - - private var _thickness: CGFloat? - public var thickness: CGFloat { - get { - if let thickness = _thickness { return thickness } - switch type { - case .heavy: - return 4 - case .medium: - return 2 - case .none: - return 0 - default: - return 1 - } - } - set { - _thickness = newValue - } - } - + public var inverted: Bool = false - // Use this to show vertical line - // Default is false - public var useVerticalLine: Bool? + public var orientation: VDS.Line.Orientation = .horizontal //-------------------------------------------------- // MARK: - Initializer @@ -108,13 +56,10 @@ import VDSColorTokens public init(type: Style) { self.type = type - self.useVerticalLine = false } public init(verticalLineOf type: Style, backgroundColor: Color? = nil) { self.type = type - self.backgroundColor = backgroundColor - self.useVerticalLine = true } //-------------------------------------------------- @@ -125,13 +70,10 @@ import VDSColorTokens case id case moleculeName case type - case backgroundColor - case backgroundColor_inverted - case color case frequency case inverted case useVerticalLine - case thickness + case orientation } //-------------------------------------------------- @@ -155,9 +97,12 @@ import VDSColorTokens self.inverted = inverted } - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - useVerticalLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalLine) - _thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) + /// adding code to look for the old useVerticalLine or the new orientation + if let useVerticalLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalLine) { + orientation = useVerticalLine ? .vertical : .horizontal + }else if let orientation = try typeContainer.decodeIfPresent(VDS.Line.Orientation.self, forKey: .orientation) { + self.orientation = orientation + } } public func encode(to encoder: Encoder) throws { @@ -167,8 +112,6 @@ import VDSColorTokens try container.encode(type, forKey: .type) try container.encode(inverted, forKey: .inverted) try container.encodeIfPresent(frequency, forKey: .frequency) - try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(useVerticalLine, forKey: .useVerticalLine) - try container.encodeIfPresent(_thickness, forKey: .thickness) + try container.encodeIfPresent(orientation == .vertical, forKey: .useVerticalLine) } } From 2e03cec5be3ed5ed51c991c56823ed7f7cb0969d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 13:27:51 -0500 Subject: [PATCH 034/112] updated ListProgressBarThin Signed-off-by: Matt Bruce --- .../Molecules/DesignedComponents/List/ListProgressBarThin.swift | 2 +- .../DesignedComponents/List/ListProgressBarThinModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift index 9b85dc4f..d9875068 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -102,7 +102,7 @@ leftHeadline.styleB1(true) leftBody.styleB2(true) rightLabel.styleB2(true) - rightBar.setStyle(.medium) + rightBar.setStyle(.primary) } //------------------------------------------------------ diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index 52c175df..d6787f52 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -39,7 +39,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { override public func setDefaults() { super.setDefaults() - rightBar.type = .medium + rightBar.type = .primary if rightBar.backgroundColor == nil { rightBar.backgroundColor = Color(uiColor: .gray) From 81a5d5099fe7528dbe7974af702300d9c9bf02ba Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 13:28:16 -0500 Subject: [PATCH 035/112] updated ListRightVariableTotalData Signed-off-by: Matt Bruce --- .../List/RightVariable/ListRightVariableTotalData.swift | 4 ++-- .../List/RightVariable/ListRightVariableTotalDataModel.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift index a749041a..9845ea16 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift @@ -44,7 +44,7 @@ override open func setupView() { super.setupView() - bar.setStyle(.heavy) + bar.setStyle(.primary) bar.widthAnchor.constraint(equalToConstant: 20).isActive = true rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) addMolecule(stack) @@ -74,7 +74,7 @@ super.reset() leftLabel.setFontStyle(.BoldBodySmall) rightLabel.setFontStyle(.RegularBodySmall) - bar.setStyle(.heavy) + bar.setStyle(.primary) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift index cebaeb34..53ede6e5 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift @@ -24,7 +24,7 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc override public func setDefaults() { super.setDefaults() rightLabel.hero = 0 - bar.type = .heavy + bar.type = .primary if bar.backgroundColor == nil { bar.backgroundColor = Color(uiColor: .mvmBlue) From aed46d592c86a8860f6d24cf716e2d0d9eaf1378 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 13:28:38 -0500 Subject: [PATCH 036/112] updated tabs and tabs model Signed-off-by: Matt Bruce --- .../Atomic/Molecules/HorizontalCombinationViews/TabBar.swift | 2 +- .../Atomic/Molecules/HorizontalCombinationViews/Tabs.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index e49006fa..bbe17e05 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -50,7 +50,7 @@ import VDSColorTokens setItems(tabs, animated: false) selectedItem = tabs[model.selectedTab] - guard let lineModel = line.lineModel else { return } + guard let lineModel = line.viewModel else { return } lineModel.inverted = model.style == .dark line.set(with: lineModel, delegateObject, additionalData) } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift index e5682e71..e6689f3d 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift @@ -174,7 +174,7 @@ import VDSColorTokens self.additionalData = additionalData selectedIndex = tabsModel?.selectedIndex ?? 0 selectionLine.backgroundColor = tabsModel?.selectedBarColor.uiColor - let lineModel = bottomLine.lineModel ?? LineModel(type: .secondary) + let lineModel = bottomLine.viewModel ?? LineModel(type: .secondary) lineModel.inverted = tabsModel?.style == .dark bottomLine.set(with: lineModel, delegateObject, additionalData) reloadData() From 5d9944b5eab6482d78026235a675a31e81738255 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:07:36 -0500 Subject: [PATCH 037/112] subclass line directly Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index c527a362..e3195aa2 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -9,22 +9,15 @@ import UIKit import VDS -@objcMembers open class Line: VDS.View, VDSMoleculeViewProtocol { +@objcMembers open class Line: VDS.Line, VDSMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var line = VDS.Line() open var viewModel: LineModel! open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? - open var orientation: VDS.Line.Orientation = .horizontal { - didSet { - viewModel.orientation = orientation - update(viewModel: viewModel) - } - } - + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -47,15 +40,6 @@ import VDS super.init(coder: coder) viewModel = LineModel(type: .primary) } - - //-------------------------------------------------- - // MARK: - Overrides - //-------------------------------------------------- - open override func setup() { - super.setup() - addSubview(line) - line.pinToSuperView() - } //-------------------------------------------------- // MARK: - Methods @@ -89,8 +73,8 @@ import VDS //-------------------------------------------------- open func viewModelDidUpdate() { surface = viewModel.surface - line.style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary - line.orientation = viewModel.orientation + style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary + orientation = viewModel.orientation } //-------------------------------------------------- From 62afe434f9a37c36fb127550b39a3862853f46df Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:07:49 -0500 Subject: [PATCH 038/112] updated to use line properties Signed-off-by: Matt Bruce --- .../UINavigationController+Extension.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift index 625f0a25..d45ca632 100644 --- a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -81,9 +81,9 @@ public extension UINavigationController { /// Returns a ShadowImage based on the line property of NavigationItemModelProtocol func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? { - guard let thickness = navigationItemModel.line?.thickness, - let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil } - return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness)) + guard let model = navigationItemModel.line else { return nil } + let line = Line(model: model, nil, nil) + return line.lineColor.image(CGSize(width: line.lineWidth, height: line.lineWidth)) } /// Convenience function for setting the navigation bar ui From 10370f3ac6840a89f97b158aecfe4b6691765519 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:35:46 -0500 Subject: [PATCH 039/112] Revert "subclass line directly" This reverts commit 5d9944b5eab6482d78026235a675a31e81738255. --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index e3195aa2..c527a362 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -9,15 +9,22 @@ import UIKit import VDS -@objcMembers open class Line: VDS.Line, VDSMoleculeViewProtocol { +@objcMembers open class Line: VDS.View, VDSMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + open var line = VDS.Line() open var viewModel: LineModel! open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? - + open var orientation: VDS.Line.Orientation = .horizontal { + didSet { + viewModel.orientation = orientation + update(viewModel: viewModel) + } + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -40,6 +47,15 @@ import VDS super.init(coder: coder) viewModel = LineModel(type: .primary) } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func setup() { + super.setup() + addSubview(line) + line.pinToSuperView() + } //-------------------------------------------------- // MARK: - Methods @@ -73,8 +89,8 @@ import VDS //-------------------------------------------------- open func viewModelDidUpdate() { surface = viewModel.surface - style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary - orientation = viewModel.orientation + line.style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary + line.orientation = viewModel.orientation } //-------------------------------------------------- From b2b33db8a986517997760a7007fef6ce24a83179 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:40:56 -0500 Subject: [PATCH 040/112] Revert "Revert "subclass line directly"" This reverts commit 10370f3ac6840a89f97b158aecfe4b6691765519. --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index c527a362..e3195aa2 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -9,22 +9,15 @@ import UIKit import VDS -@objcMembers open class Line: VDS.View, VDSMoleculeViewProtocol { +@objcMembers open class Line: VDS.Line, VDSMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var line = VDS.Line() open var viewModel: LineModel! open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? - open var orientation: VDS.Line.Orientation = .horizontal { - didSet { - viewModel.orientation = orientation - update(viewModel: viewModel) - } - } - + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -47,15 +40,6 @@ import VDS super.init(coder: coder) viewModel = LineModel(type: .primary) } - - //-------------------------------------------------- - // MARK: - Overrides - //-------------------------------------------------- - open override func setup() { - super.setup() - addSubview(line) - line.pinToSuperView() - } //-------------------------------------------------- // MARK: - Methods @@ -89,8 +73,8 @@ import VDS //-------------------------------------------------- open func viewModelDidUpdate() { surface = viewModel.surface - line.style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary - line.orientation = viewModel.orientation + style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary + orientation = viewModel.orientation } //-------------------------------------------------- From 6da75b26bd94176658092e2e6baca48486ec3cd2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:51:12 -0500 Subject: [PATCH 041/112] don't call super to draw if model is set to none Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index e3195aa2..c3ce3d7f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -68,6 +68,11 @@ import VDS ) } + open override func draw(_ rect: CGRect) { + guard viewModel.type != .none else { return } + super.draw(rect) + } + //-------------------------------------------------- // MARK: - VDSMoleculeViewProtocol //-------------------------------------------------- From 2a34fa433dbeb29e0dbbfa1cd63a56bd7f981556 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 20 Sep 2023 14:51:21 -0500 Subject: [PATCH 042/112] updated for line Signed-off-by: Matt Bruce --- .../Headers/H2/HeadersH2PricingTwoRows.swift | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRows.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRows.swift index 1baf057c..f38793eb 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRows.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRows.swift @@ -42,6 +42,13 @@ import Foundation return Stack.createStack(with: [headline, horizontalStack], spacing: 8) }() + + //------------------------------------------------------- + // MARK: - Constraints + //------------------------------------------------------- + public var verticalLine1HeightConstraint: NSLayoutConstraint? + public var verticalLine2HeightConstraint: NSLayoutConstraint? + //------------------------------------------------------- // MARK: - Lifecycle //------------------------------------------------------- @@ -51,11 +58,10 @@ import Foundation body.lineBreakMode = .byTruncatingTail body2.lineBreakMode = .byTruncatingTail body3.lineBreakMode = .byTruncatingTail - - verticalLine1.widthConstraint?.isActive = true - verticalLine1.backgroundColor = .mvmBlack - verticalLine2.widthConstraint?.isActive = true - verticalLine2.backgroundColor = .mvmBlack + + // setup lines + verticalLine1.orientation = .vertical + verticalLine2.orientation = .vertical addMolecule(stack) stack.restack() @@ -80,13 +86,13 @@ import Foundation } open func setLineHeight() { - verticalLine1.heightConstraint?.isActive = false - verticalLine1.heightConstraint = verticalLine1.heightAnchor.constraint(equalTo: body2.heightAnchor, multiplier: 1) - verticalLine1.heightConstraint?.isActive = true + verticalLine1HeightConstraint?.isActive = false + verticalLine1HeightConstraint = verticalLine1.heightAnchor.constraint(equalTo: body2.heightAnchor, multiplier: 1) + verticalLine1HeightConstraint?.isActive = true - verticalLine2.heightConstraint?.isActive = false - verticalLine2.heightConstraint = verticalLine2.heightAnchor.constraint(equalTo: body3.heightAnchor, multiplier: 1) - verticalLine2.heightConstraint?.isActive = true + verticalLine2HeightConstraint?.isActive = false + verticalLine2HeightConstraint = verticalLine2.heightAnchor.constraint(equalTo: body3.heightAnchor, multiplier: 1) + verticalLine2HeightConstraint?.isActive = true } //---------------------------------------------------- From 32ba75e731c7118e5c7df7af9fa20fd884771e19 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 21 Sep 2023 19:17:06 +0530 Subject: [PATCH 043/112] updated with code review comments & created accessibility handler behaviour --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../Accessibility/AccessibilityHandler.swift | 200 ++++++++++-------- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 3 +- .../Atomic/Atoms/Selectors/ToggleModel.swift | 5 +- .../Protocols/MoleculeViewProtocol.swift | 9 +- .../Protocols/AccessibilityProtocol.swift | 5 - MVMCoreUI/OtherHandlers/CoreUIObject.swift | 7 +- .../MVMCoreUISession+Extension.swift | 22 ++ MVMCoreUI/OtherHandlers/MVMCoreUISession.m | 2 + 9 files changed, 151 insertions(+), 106 deletions(-) create mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4e04dafc..bb67be60 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,6 +168,7 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; + 71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */; }; 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; @@ -755,6 +756,7 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; + 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUISession+Extension.swift"; sourceTree = ""; }; 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; @@ -2277,6 +2279,7 @@ D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */, D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */, D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */, + 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */, D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */, D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */, @@ -3093,6 +3096,7 @@ D29C559025C095210082E7D6 /* Video.swift in Sources */, D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */, AA104B1C24474A76004D2810 /* HeadersH2ButtonsModel.swift in Sources */, + 71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, AAE7270C24AC8B8500A3ED0E /* HeadersH2CaretLinkModel.swift in Sources */, BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */, diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index b476324f..96664c06 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -11,44 +11,13 @@ import Combine import MVMCore import WebKit -public enum AccessibilityNotificationType: String, Codable { - - case controllerChanged, layoutChanged, screenChanged, announcement - - //TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher. - //https://developer.apple.com/forums/thread/132699, - //https://developer.apple.com/forums/thread/655359 - //By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push - var delay: Double { - switch self { - case .controllerChanged: - return 1.5 - default: - return 0.0 - } - } - - var accessibilityNotification: UIAccessibility.Notification { - switch self { - case .announcement: - return .announcement - case .screenChanged: - return .screenChanged - case .layoutChanged, .controllerChanged: - return .layoutChanged - } - } -} - -public typealias ArgumentHandler = ((NavigationOperationType?) -> Any?) - public class AccessbilityOperation: MVMCoreOperation { private let argument: Any? - private let notificationType: AccessibilityNotificationType + private let notificationType: UIAccessibility.Notification private var timerSource: DispatchSourceTimer? - public init(notificationType: AccessibilityNotificationType, argument: Any?) { + public init(notificationType: UIAccessibility.Notification, argument: Any?) { self.notificationType = notificationType self.argument = argument } @@ -61,15 +30,15 @@ public class AccessbilityOperation: MVMCoreOperation { timerSource = DispatchSource.makeTimerSource() timerSource?.setEventHandler { Task { @MainActor [weak self] in - if !(self?.isCancelled ?? false), let notification = self?.notificationType.accessibilityNotification { - UIAccessibility.post(notification: notification, argument: self?.argument) - self?.markAsFinished() - } else { + guard let self = self, !self.isCancelled else { self?.stop() + return } + UIAccessibility.post(notification: self.notificationType, argument: self.argument) + self.markAsFinished() } } - timerSource?.schedule(deadline: .now() + notificationType.delay) + timerSource?.schedule(deadline: .now()) timerSource?.activate() } @@ -80,17 +49,16 @@ public class AccessbilityOperation: MVMCoreOperation { } } -public enum NavigationOperationType { case `default`, tab } - open class AccessibilityHandler { public static func shared() -> Self? { guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } return MVMCoreActionUtility.fatalClassCheck(object: shared) } - public weak var delegate: MVMCoreViewControllerProtocol? + public var previousAccessiblityElement: Any? public var anyCancellable: Set = [] + public weak var delegate: MVMCoreViewControllerProtocol? private var accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() @@ -100,16 +68,16 @@ open class AccessibilityHandler { private var accessibilityId: String? private var announcementText: String? private var hasTopNotitificationInPage: Bool = false - + public init() { - registerWithNotificationCenter() + registerWithResponseLoaded() registerForPageChanges() registerForFocusChanges() - registerForTopNotificationsChanges() } - - /// Registers with the notification center to know when json is updated. - private func registerWithNotificationCenter() { + + // MARK: - Register with Accessibility Handler listeners + /// Registers with the notification center to know when json is updated and to capture previous accessbility focused id & announcment text + private func registerWithResponseLoaded() { NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded)) .sink { [weak self] notification in self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId") @@ -117,20 +85,15 @@ open class AccessibilityHandler { }.store(in: &anyCancellable) } - /// Registers to know when pages change. - open func registerForPageChanges() { - MVMCoreNavigationHandler.shared()?.addDelegate(self) - } - private func registerForFocusChanges() { //Since foucs shifted to other elements cancelling existing focus shift notifications if any NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) - .sink { [weak self] notification in + .sink { [weak self] _ in self?.cancelAllOperations() }.store(in: &anyCancellable) } - - private func registerForTopNotificationsChanges() { + + func registerForTopNotificationsChanges() { NotificationHandler.shared()?.onNotificationWillShow .sink { [weak self] (_, model) in self?.hasTopNotitificationInPage = true @@ -150,6 +113,49 @@ open class AccessibilityHandler { }.store(in: &anyCancellable) } + /// Registers to know when pages change. + open func registerForPageChanges() { + NavigationHandler.shared() + .onNavigation + .sink { [self] (event, operation) in + switch event { + case .willNavigate: + willNavigate(operation) + @unknown default: + break + } + }.store(in: &anyCancellable) + } + + private func willNavigate(_ operation: NavigationOperation) { + previousAccessiblityElement = nil + if let announcementText { + post(notification: .announcement, argument: announcementText) + } + if let subNavManagerController = operation.toNavigationControllerViewControllers?.last as? SubNavManagerController { + delegate = subNavManagerController.getCurrentViewController() as? MVMCoreViewControllerProtocol + } else { + delegate = operation.toNavigationControllerViewControllers?.last as? MVMCoreViewControllerProtocol + } + } + + /*private func didNavigate(_ operation: NavigationOperation) { + guard UIAccessibility.isVoiceOverRunning, + let viewController = operation.toNavigationControllerViewControllers?.last, + canPostAccessbilityNotification(for: viewController) else { return } + delegate = viewController as? MVMCoreViewControllerProtocol + guard let view = operation.toNavigationControllerViewControllers?.last?.view else { return } + view.accessibilityElements = getAccessibilityElementsOnScreen() + if hasTopNotitificationInPage { + previousAccessiblityElement = getFirstFocusedElementOnScreen() + } else { + let accessbilityElement = getUIElementBasedOn(id: accessibilityId) + post(notification: .screenChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) + accessibilityId = nil + } + }*/ + + // MARK: - Accessibility Handler operation events open func capturePreviousFocusElement() { previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) } @@ -166,57 +172,27 @@ open class AccessibilityHandler { accessibilityOperationQueue.cancelAllOperations() } - public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) { + public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) add(operation: accessbilityOperation) } - //To get first foucs element on the screen + //To get first focus element on the screen open func getFirstFocusedElementOnScreen() -> Any? { (delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar } //Subclass can decide to trigger Accessibility notification on screen change. open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } -} - -extension AccessibilityHandler: MVMCorePresentationDelegateProtocol { - open func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { - previousAccessiblityElement = nil - delegate = viewController as? MVMCoreViewControllerProtocol - if let announcementText { - post(notification: .announcement, argument: announcementText) - } - } - - open func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - guard UIAccessibility.isVoiceOverRunning, - canPostAccessbilityNotification(for: viewController) else { return } - //TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count. If we have top notification in page on pageLoad, we have postnotification for shifting the focus so in this case we are not posting accessiblity notifcation - if hasTopNotitificationInPage { - previousAccessiblityElement = getFirstFocusedElementOnScreen() - } else { - let accessbilityElement = getAccessbilityFocusedElement() - let operationType: NavigationOperationType = navigationController.children.count == 1 ? .tab : .default //TODO: - need to identify the operationType - post(notification: operationType == .tab ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) - accessibilityId = nil - } - } -} - -extension AccessibilityHandler { - - private func getAccessbilityFocusedElement() -> UIView? { - guard let accessibilityModels: [any AccessibilityElementProtocol] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil } - guard !accessibilityModels.isEmpty, - let accessibilityModel = (accessibilityModels.filter { $0.id == accessibilityId }).first as? MoleculeModelProtocol else { - return nil - } + func getPreDefinedFocusedElementIfAny() -> UIView? { + guard let accessibilityId, let models: [any Identifiable] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil } + guard !models.isEmpty, + let model = (models.filter { ($0.id as? String) == accessibilityId }).first else { return nil } return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in - guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.getMoleculeModel() as? (any AccessibilityElementProtocol), - moleculeModel.id == (accessibilityModel as? (any AccessibilityElementProtocol))?.id else { + guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel as? (any Identifiable), + (moleculeModel.id as? String) == (model.id as? String) else { return false } return true @@ -224,6 +200,44 @@ extension AccessibilityHandler { } } +// MARK: - Accessibility Handler Behaviour +///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. +struct AccessibilityHandlerBehaviorModel: PageBehaviorModelProtocol { + + var shouldAllowMultipleInstances = false + static var identifier = "accessibilityHandlerBehaviorModel" +} + +class AccessibilityHandlerBehavior: PageVisibilityBehavior { + + required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } + + public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { + let viewController = updateAccessibilityViews(delegateObject) + AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() ?? AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen()) + } + + private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) -> UIViewController? { + var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController] + var viewController: UIViewController? + if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { + var managerControllerViews = [Any?]() + managerControllerViews.append(managerController.navigationController) + managerControllerViews.append(managerController.tabs) + managerControllerViews.append(contentsOf: managerController.view.subviews) + managerController.view.accessibilityElements = managerControllerViews.compactMap { $0 } + } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { + accessibilityElements.append(controller.navigationController) + accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + controller.view.accessibilityElements = accessibilityElements + viewController = controller + } + return viewController + } +} + +// MARK: - Helpers extension UIView { private func getNestedSubviews() -> [T] { diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 359df799..d84efad6 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -372,7 +372,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - self.model = model guard let model = model as? ToggleModel else { return } @@ -422,5 +421,5 @@ extension Toggle { extension Toggle: MoleculeViewModelProtocol { - public func getMoleculeModel() -> MoleculeModelProtocol? { model } + public var moleculeModel: MoleculeModelProtocol? { model } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 3757dee5..fc267cff 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -7,7 +7,7 @@ // -public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, AccessibilityElementProtocol { +public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Identifiable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -32,7 +32,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? - public var id: String //-------------------------------------------------- // MARK: - Keys @@ -56,7 +55,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit case offKnobTintColor case fieldKey case groupName - case id } //-------------------------------------------------- @@ -132,7 +130,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit } enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false - id = try typeContainer.decode(forKey: .id, default: { UUID().uuidString }()) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 47e0ca07..dbd6b2df 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -104,5 +104,12 @@ public extension ModelRegistry { public protocol MoleculeViewModelProtocol: UIView { - func getMoleculeModel() -> MoleculeModelProtocol? + var moleculeModel: MoleculeModelProtocol? { get } +} + +extension MoleculeViewModelProtocol { + + var moleculeModel: MoleculeModelProtocol? { + get { nil } + } } diff --git a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift index 2a6456dd..6c7ada50 100644 --- a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift +++ b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift @@ -12,8 +12,3 @@ import Foundation /// Should return the argument to use for posting a layout change. func getAccessibilityLayoutChangedArgument() -> Any? } - -public protocol AccessibilityElementProtocol: Identifiable { - - var id: String { get set } -} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index 7480b3ad..dd82a34f 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -11,7 +11,11 @@ import MVMCore @objcMembers open class CoreUIObject: MVMCoreObject { public var alertHandler: AlertHandler? - public var topNotificationHandler: NotificationHandler? + public var topNotificationHandler: NotificationHandler? { + didSet { + accessibilityHandler?.registerForTopNotificationsChanges() + } + } public var accessibilityHandler: AccessibilityHandler? open override func defaultInitialSetup() { @@ -24,5 +28,6 @@ import MVMCore viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler() alertHandler = AlertHandler() + accessibilityHandler = AccessibilityHandler() } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift b/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift new file mode 100644 index 00000000..89aae858 --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift @@ -0,0 +1,22 @@ +// +// MVMCoreUISession.swift +// MVMCoreUI +// +// Created by Bandaru, Krishna Kishore on 16/09/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc public extension MVMCoreUISession { + + @objc func applyGlobalMVMCoreUIBehaviors(to viewController: UIViewController) { + + guard var behaviorController = viewController as? PageBehaviorHandlerProtocol, let delegateObject = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() else { return } + + let accessibilityHandlerBehavior = AccessibilityHandlerBehavior(model: AccessibilityHandlerBehaviorModel(), delegateObject: (delegateObject as! MVMCoreUIDelegateObject)) + + let behaviors = behaviorController.behaviors ?? [] + behaviorController.behaviors = behaviors + [accessibilityHandlerBehavior] + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m index 42a3e769..02ed8df2 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m @@ -9,6 +9,7 @@ #import "MVMCoreUISession.h" #import "MFLoadingViewController.h" #import "NSLayoutConstraint+MFConvenience.h" +#import @import MVMCore.MVMCoreObject; @interface MVMCoreUISession () @@ -60,6 +61,7 @@ - (void)applyGlobalBehaviorsToController:(nonnull UIViewController *)viewController { // Allow extending frameworks to apply behaviors to add cross cutting concerns to the base controllers. + [self applyGlobalMVMCoreUIBehaviorsTo:viewController]; } @end From ad3112d984719277908c6482139929e871f965d8 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 11:34:28 -0500 Subject: [PATCH 044/112] added id Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift index de19faab..cbb9a1b9 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -10,12 +10,11 @@ import Foundation import VDS open class BadgeModel: MoleculeModelProtocol { - - //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public static var identifier: String = "badge" + public var id: String = UUID().uuidString public var backgroundColor: Color? public var text: String = "" public var maxWidth: CGFloat? @@ -24,12 +23,13 @@ open class BadgeModel: MoleculeModelProtocol { public var surface: Surface = .light private enum CodingKeys: String, CodingKey { - case text, fillColor, surface, numberOfLines, maxWidth + case id, text, fillColor, surface, numberOfLines, maxWidth } required public convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString text = try container.decode(String.self, forKey: .text) fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light @@ -39,6 +39,7 @@ open class BadgeModel: MoleculeModelProtocol { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) try container.encode(text, forKey: .text) try container.encode(fillColor, forKey: .fillColor) try container.encode(surface, forKey: .surface) From 8143cc7d9e36a368648637d8de06da7ddae983ea Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 11:34:38 -0500 Subject: [PATCH 045/112] re arranged files Signed-off-by: Matt Bruce --- MVMCoreUI.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 46156d9d..d6404d24 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -2220,8 +2220,8 @@ AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, B4CC8FBC29DF34680005D28B /* Badge.swift */, - EA985C3D2970938F00F2FF2E /* Tilelet.swift */, B4CC8FBE29DF34730005D28B /* BadgeModel.swift */, + EA985C3D2970938F00F2FF2E /* Tilelet.swift */, EA985C3F2970939A00F2FF2E /* TileletModel.swift */, ); path = Views; From 89f65ebe3dbe6785968e2d46cd78e7ce269c106a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 14:41:35 -0500 Subject: [PATCH 046/112] added codable Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 6c143d9c..b3500df1 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -16,3 +16,4 @@ extension Icon.Size: Codable {} extension TileContainer.BackgroundColor: Codable {} extension TileContainer.Padding: Codable {} extension TileContainer.AspectRatio: Codable {} +extension TextLink.Size: Codable {} From 0f85c96201024930bb272ee85c0e786b318ddefb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 14:41:42 -0500 Subject: [PATCH 047/112] updated link Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Buttons/Link/Link.swift | 92 +++++++------------ .../Atomic/Atoms/Buttons/Link/LinkModel.swift | 69 ++------------ 2 files changed, 38 insertions(+), 123 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index 4f4a8b34..8c1e5f4a 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -8,83 +8,53 @@ import UIKit import VDSColorTokens +import VDS + +@objcMembers open class Link: VDS.TextLink, VDSMoleculeViewProtocol { -@objcMembers open class Link: Button { //-------------------------------------------------- - // MARK: - Draw + // MARK: - Public Properties //-------------------------------------------------- + open var viewModel: LinkModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? - open override func draw(_ rect: CGRect) { + //-------------------------------------------------- + // MARK: - Public Functions + //-------------------------------------------------- + open func viewModelDidUpdate() { + isEnabled = viewModel.enabled + size = viewModel.size + text = viewModel.title - guard let textRect = titleLabel?.frame, - let context = UIGraphicsGetCurrentContext() - else { return } - - // Set line to the same color as the text - if let color = titleLabel?.textColor?.cgColor { - context.setStrokeColor(color) + onClick = { [weak self] control in + guard let self else { return } + MVMCoreUIActionHandler.performActionUnstructured(with: self.viewModel.action, + sourceModel: self.viewModel, + additionalData: self.additionalData, + delegateObject: self.delegateObject) } - - // x should be according to the text, not the button - let x = textRect.origin.x - - // Line is 0 point below the text - let y = textRect.origin.y + textRect.size.height - - context.move(to: CGPoint(x: x, y: y)) - context.addLine(to: CGPoint(x: x + textRect.size.width, y: y)) - context.strokePath() } - - open override var intrinsicContentSize: CGSize { - guard let size = titleLabel?.intrinsicContentSize else { return super.intrinsicContentSize } - return CGSize(width: size.width, height: size.height + 1) - } - + //-------------------------------------------------- - // MARK: - MoleculeViewProtocol + // MARK: - Overrides //-------------------------------------------------- - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) + open override func updateAccessibility() { + super.updateAccessibility() - guard let model = model as? LinkModel else { return } - - setTitle(model.title, for: .normal) - if let accessibilityText = model.accessibilityText { + if let viewModel, let accessibilityText = viewModel.accessibilityText { accessibilityLabel = accessibilityText } - setTitleColor((model.inverted ? model.enabledColor_inverted : model.enabledColor).uiColor, for: .normal) - setTitleColor((model.inverted ? model.disabledColor_inverted : model.disabledColor).uiColor, for: .disabled) - setTitleColor((model.inverted ? model.activeColor_inverted : model.activeColor).uiColor, for: .highlighted) - isEnabled = model.enabled - titleLabel?.font = model.getFont(model.size) - set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } - open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 31 } -} + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 31 } -// MARK: - MVMCoreViewProtocol -extension Link { + open func updateView(_ size: CGFloat) { } - open override func updateView(_ size: CGFloat) { - super.updateView(size) - } - - open override func setupView() { - super.setupView() - backgroundColor = .clear - contentMode = .redraw - setTitleColor(VDSColor.elementsPrimaryOnlight, for: .normal) - setTitleColor(VDSColor.interactiveDisabledOnlight, for: .disabled) - setTitleColor(VDSColor.interactiveActiveOnlight, for: .highlighted) - titleLabel?.numberOfLines = 1 - titleLabel?.lineBreakMode = .byTruncatingTail - titleLabel?.textAlignment = .left - contentHorizontalAlignment = .left - contentVerticalAlignment = .top - } + open func setupView() {} } // MARK: - MVMCoreUIViewConstrainingProtocol diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift index 62ba0422..7e9a4640 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift @@ -7,7 +7,7 @@ // import UIKit -import VDSColorTokens +import VDS open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- @@ -23,15 +23,9 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode public var accessibilityText: String? public var action: ActionModelProtocol public var enabled = true - public var enabledColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) - public var enabledColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) - public var disabledColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) - public var disabledColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) - public var activeColor = Color(uiColor: VDSColor.interactiveActiveOnlight) - public var activeColor_inverted = Color(uiColor: VDSColor.interactiveActiveOndark) public var inverted = false - public var size:linkFontSize = linkFontSize.small + public var size: TextLink.Size = .small public var shouldMaskRecordedView: Bool? = false @@ -57,34 +51,10 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode case title case action case enabled - case enabledColor - case enabledColor_inverted - case disabledColor - case disabledColor_inverted - case activeColor - case activeColor_inverted case inverted case size case shouldMaskRecordedView } - - public enum linkFontSize: String, Codable { - case small - case large - } - - //-------------------------------------------------- - // MARK: - Method - //-------------------------------------------------- - - func getFont(_ type: linkFontSize) -> UIFont { - switch type { - case .small: - return MFStyler.fontRegularBodySmall() - case .large: - return MFStyler.fontRegularBodyLarge() - } - } //-------------------------------------------------- // MARK: - Codec @@ -108,30 +78,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode self.inverted = inverted } - if let enabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor) { - self.enabledColor = enabledColor - } - - if let enabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor_inverted) { - self.enabledColor_inverted = enabledColor_inverted - } - - if let disabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) { - self.disabledColor = disabledColor - } - - if let disabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor_inverted) { - self.disabledColor_inverted = disabledColor_inverted - } - - if let activeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .activeColor) { - self.activeColor = activeColor - } - - if let activeColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .activeColor_inverted) { - self.activeColor_inverted = activeColor_inverted - } - if let size = try typeContainer.decodeIfPresent(linkFontSize.self, forKey: .size) { + if let size = try typeContainer.decodeIfPresent(TextLink.Size.self, forKey: .size) { self.size = size } @@ -148,13 +95,11 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode try container.encodeModel(action, forKey: .action) try container.encode(inverted, forKey: .inverted) try container.encode(enabled, forKey: .enabled) - try container.encode(enabledColor, forKey: .enabledColor) - try container.encode(enabledColor_inverted, forKey: .enabledColor_inverted) - try container.encode(disabledColor, forKey: .disabledColor) - try container.encode(disabledColor_inverted, forKey: .disabledColor_inverted) - try container.encode(activeColor, forKey: .activeColor) - try container.encode(activeColor_inverted, forKey: .activeColor_inverted) try container.encodeIfPresent(size, forKey: .size) try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) } } + +extension LinkModel { + public var surface: Surface { inverted ? .dark : .light } +} From f4221c0215a9cdbfa05633069a0a13c031183af7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 14:41:50 -0500 Subject: [PATCH 048/112] updated external link Signed-off-by: Matt Bruce --- .../Atoms/Buttons/Link/ExternalLink.swift | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift index e051cf32..9e5cd108 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift @@ -11,29 +11,24 @@ import UIKit open class ExternalLink: Link { //-------------------------------------------------- - // MARK: - Properties + // MARK: - Public Properties //-------------------------------------------------- - - public var exportImageView: UIImageView? - + open var exportImageView: UIImageView? + open var exportImageHeight: NSLayoutConstraint? + open var exportImageWidth: NSLayoutConstraint? + //-------------------------------------------------- - // MARK: - MoleculeViewProtocol + // MARK: - Overrides //-------------------------------------------------- - - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - - guard let model = model as? ExternalLinkModel else { return } - - exportImageView?.tintColor = titleColor(for: model.enabled ? .normal : .disabled) + open override func viewModelDidUpdate() { + super.viewModelDidUpdate() + exportImageView?.tintColor = textColor + exportImageWidth?.constant = textStyle.lineHeight + exportImageHeight?.constant = textStyle.lineHeight } - - //-------------------------------------------------- - // MARK: - MVMCoreViewProtocol - //-------------------------------------------------- - - open override func setupView() { - super.setupView() + + open override func setup() { + super.setup() let image = MVMCoreUIUtility.imageNamed("externalLink") exportImageView = UIImageView(image: image?.withRenderingMode(.alwaysTemplate)) @@ -46,10 +41,10 @@ open class ExternalLink: Link { addSubview(exportIcon) trailingAnchor.constraint(greaterThanOrEqualTo: exportIcon.trailingAnchor).isActive = true - if let titleLabel = titleLabel { - let dimension = titleLabel.font.pointSize - exportIcon.heightAnchor.constraint(equalToConstant: dimension).isActive = true - exportIcon.widthAnchor.constraint(equalToConstant: dimension).isActive = true + exportImageHeight = exportIcon.heightAnchor.constraint(equalToConstant: textStyle.pointSize).activate() + exportImageWidth = exportIcon.widthAnchor.constraint(equalToConstant: textStyle.pointSize).activate() + + if let titleLabel { exportIcon.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 4).isActive = true exportIcon.bottomAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor, constant: 3).isActive = true } From dff7cc6d3120905e78d5e0886bf276fc64afb919 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 14:46:28 -0500 Subject: [PATCH 049/112] updated accessibility Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index 8c1e5f4a..86776016 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -26,6 +26,7 @@ import VDS isEnabled = viewModel.enabled size = viewModel.size text = viewModel.title + surface = viewModel.surface onClick = { [weak self] control in guard let self else { return } @@ -42,8 +43,14 @@ import VDS open override func updateAccessibility() { super.updateAccessibility() - if let viewModel, let accessibilityText = viewModel.accessibilityText { - accessibilityLabel = accessibilityText + if let viewModel { + if let accessibilityText = viewModel.accessibilityText { + self.accessibilityLabel = accessibilityText + } + + if let accessibilityIdentifier = viewModel.accessibilityIdentifier { + self.accessibilityIdentifier = accessibilityIdentifier + } } } From e97582ee1e183993ee4efabc65fc2faad5b7c233 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 21 Sep 2023 14:59:00 -0500 Subject: [PATCH 050/112] added accessibility Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/Badge.swift | 18 ++++++++++++++++-- MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 5 ++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift index 0e6d1075..1306074f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift @@ -14,7 +14,7 @@ import Combine open class Badge: VDS.Badge, VDSMoleculeViewProtocol { // public typealias ViewModel = type //-------------------------------------------------- - // MARK: - Properties + // MARK: - Public Properties //-------------------------------------------------- public var viewModel: BadgeModel! @@ -23,7 +23,7 @@ open class Badge: VDS.Badge, VDSMoleculeViewProtocol { public var additionalData: [AnyHashable : Any]? //-------------------------------------------------- - // MARK: - Public + // MARK: - Public Methods //-------------------------------------------------- public func viewModelDidUpdate() { @@ -35,6 +35,20 @@ open class Badge: VDS.Badge, VDSMoleculeViewProtocol { } public func updateView(_ size: CGFloat) {} + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func updateAccessibility() { + super.updateAccessibility() + + if let viewModel { + if let accessibilityText = viewModel.accessibilityText { + self.accessibilityLabel = accessibilityText + } + } + } + } //to deal with how it's parent constrains this control diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift index cbb9a1b9..37344dad 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -17,13 +17,14 @@ open class BadgeModel: MoleculeModelProtocol { public var id: String = UUID().uuidString public var backgroundColor: Color? public var text: String = "" + public var accessibilityText: String? public var maxWidth: CGFloat? public var numberOfLines: Int = 1 public var fillColor = Badge.FillColor.red public var surface: Surface = .light private enum CodingKeys: String, CodingKey { - case id, text, fillColor, surface, numberOfLines, maxWidth + case id, text, accessibilityText, fillColor, surface, numberOfLines, maxWidth } required public convenience init(from decoder: Decoder) throws { @@ -31,6 +32,7 @@ open class BadgeModel: MoleculeModelProtocol { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString text = try container.decode(String.self, forKey: .text) + accessibilityText = try container.decodeIfPresent(String.self, forKey: .accessibilityText) fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 1 @@ -41,6 +43,7 @@ open class BadgeModel: MoleculeModelProtocol { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(text, forKey: .text) + try container.encode(accessibilityText, forKey: .accessibilityText) try container.encode(fillColor, forKey: .fillColor) try container.encode(surface, forKey: .surface) try container.encode(numberOfLines, forKey: .numberOfLines) From e9dc771eea5c8d0e50d76cd362af9ed552bae184 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 22 Sep 2023 17:16:09 +0530 Subject: [PATCH 051/112] removed unused code & added support for predefinedfocused elements from server --- .../Accessibility/AccessibilityHandler.swift | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 96664c06..fde20909 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -65,9 +65,9 @@ open class AccessibilityHandler { queue.maxConcurrentOperationCount = 1 return queue }() - private var accessibilityId: String? + private(set) var accessibilityId: String? private var announcementText: String? - private var hasTopNotitificationInPage: Bool = false + private(set) var hasTopNotitificationInPage: Bool = false public init() { registerWithResponseLoaded() @@ -121,7 +121,7 @@ open class AccessibilityHandler { switch event { case .willNavigate: willNavigate(operation) - @unknown default: + default: break } }.store(in: &anyCancellable) @@ -138,22 +138,6 @@ open class AccessibilityHandler { delegate = operation.toNavigationControllerViewControllers?.last as? MVMCoreViewControllerProtocol } } - - /*private func didNavigate(_ operation: NavigationOperation) { - guard UIAccessibility.isVoiceOverRunning, - let viewController = operation.toNavigationControllerViewControllers?.last, - canPostAccessbilityNotification(for: viewController) else { return } - delegate = viewController as? MVMCoreViewControllerProtocol - guard let view = operation.toNavigationControllerViewControllers?.last?.view else { return } - view.accessibilityElements = getAccessibilityElementsOnScreen() - if hasTopNotitificationInPage { - previousAccessiblityElement = getFirstFocusedElementOnScreen() - } else { - let accessbilityElement = getUIElementBasedOn(id: accessibilityId) - post(notification: .screenChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen()) - accessibilityId = nil - } - }*/ // MARK: - Accessibility Handler operation events open func capturePreviousFocusElement() { @@ -210,30 +194,49 @@ struct AccessibilityHandlerBehaviorModel: PageBehaviorModelProtocol { class AccessibilityHandlerBehavior: PageVisibilityBehavior { + private var delegateObj: MVMCoreUIDelegateObject? + private var anyCancellable: Set = [] + required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { - let viewController = updateAccessibilityViews(delegateObject) - AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() ?? AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen()) + guard let controller = delegateObject?.moleculeDelegate as? UIViewController, + (AccessibilityHandler.shared()?.canPostAccessbilityNotification(for: controller) ?? true), + AccessibilityHandler.shared()?.accessibilityId == nil else { return } + if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { + AccessibilityHandler.shared()?.previousAccessiblityElement = AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen() + } else { + AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen()) + } + delegateObj = delegateObject } - private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) -> UIViewController? { - var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController] - var viewController: UIViewController? + ///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered. + ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain + func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + updateAccessibilityViews(delegateObject) //To track FAB & HAB elements on UI + guard let accessibilityElement = AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() else { return } + if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { + AccessibilityHandler.shared()?.previousAccessiblityElement = accessibilityElement + } else { + AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: accessibilityElement) + } + } + + private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { + var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { - var managerControllerViews = [Any?]() - managerControllerViews.append(managerController.navigationController) - managerControllerViews.append(managerController.tabs) - managerControllerViews.append(contentsOf: managerController.view.subviews) - managerController.view.accessibilityElements = managerControllerViews.compactMap { $0 } + accessibilityElements.append(managerController.navigationController) + accessibilityElements.append(managerController.tabs) + accessibilityElements.append(contentsOf: managerController.view.subviews) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { accessibilityElements.append(controller.navigationController) accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - controller.view.accessibilityElements = accessibilityElements - viewController = controller + controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } } - return viewController } } From c1392f425335c64c73c33af9ee9b780139872818 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Sat, 23 Sep 2023 17:34:49 +0530 Subject: [PATCH 052/112] added missing code --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index fde20909..987eca37 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -200,6 +200,7 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { + updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, (AccessibilityHandler.shared()?.canPostAccessbilityNotification(for: controller) ?? true), AccessibilityHandler.shared()?.accessibilityId == nil else { return } From 44d2aa630ec5efdff23301bec8b9993c76434426 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Mon, 25 Sep 2023 16:26:25 +0530 Subject: [PATCH 053/112] refactored PreDefinedFocusedelement func --- .../Accessibility/AccessibilityHandler.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 987eca37..0ed3348b 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -171,12 +171,17 @@ open class AccessibilityHandler { open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } func getPreDefinedFocusedElementIfAny() -> UIView? { - guard let accessibilityId, let models: [any Identifiable] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil } - guard !models.isEmpty, - let model = (models.filter { ($0.id as? String) == accessibilityId }).first else { return nil } + guard let accessibilityId else { return nil } + var modelElement: MoleculeModelProtocol? + ((delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate)?.getRootMolecules().depthFirstTraverse(options: .leafNodesOnly, depth: 0) { index, model, stop in + if model.id == accessibilityId { + modelElement = model + stop = true + } + } return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in - guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel as? (any Identifiable), - (moleculeModel.id as? String) == (model.id as? String) else { + guard let modelElement, let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel, + moleculeModel.id == modelElement.id else { return false } return true From 9c111471a802c9d60fce62b0900e3e0f35b7de21 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Sat, 23 Sep 2023 17:32:36 +0530 Subject: [PATCH 054/112] added custom button rotor for molecular views --- .../Accessibility/AccessibilityHandler.swift | 55 +++++++++++++++++++ .../AccessibilityModelProtocol.swift | 18 ++++++ .../Protocols/MoleculeViewProtocol.swift | 4 +- MVMCoreUI/BaseClasses/Button.swift | 5 ++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 0ed3348b..83b0347a 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -201,9 +201,12 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { private var delegateObj: MVMCoreUIDelegateObject? private var anyCancellable: Set = [] + private var accessibilityButtons: [Any]? + private var currentRotorIndex: Int = 0 required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } + //MARK: - PageVisibiltyBehaviour public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, @@ -229,6 +232,58 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { } } + //MARK: - Private Methods + private func identifyAndPrepareForButtonRotor() { + let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? UIViewController) + var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] + var currentIndexPath: IndexPath? + rotorElements = (currentViewController as? MoleculeListTemplate)?.templateModel?.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in + if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = (currentViewController as? MoleculeListTemplate)?.getIndexPath(for: listModel) { currentIndexPath = indexPath + } + var result = result + if (model.accessibilityTraits?.contains(.button) ?? false), let currentIndexPath { + result.append((model, currentIndexPath)) + } + return result + }) ?? [] + var accessibilityButtons: [Any?]? = currentViewController?.navigationItem.leftBarButtonItems ?? [] + accessibilityButtons?.append(contentsOf: currentViewController?.navigationItem.rightBarButtonItems ?? []) + if let tabs = (currentViewController as? SubNavManagerController)?.tabs { + accessibilityButtons?.append(contentsOf: tabs.subviews.filter { $0.accessibilityTraits.contains(.button) }) + } + accessibilityButtons?.append(contentsOf: rotorElements) + if let tabBarHidden = (delegateObj?.moleculeDelegate as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { + accessibilityButtons?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { $0.accessibilityTraits.contains(.button)}) + } + self.accessibilityButtons = accessibilityButtons?.compactMap { $0 } + currentViewController?.navigationController?.accessibilityCustomRotors = [createRotorForButtons()].compactMap { $0 } + } + + private func createRotorForButtons() -> UIAccessibilityCustomRotor? { + guard let accessibilityButtons, !accessibilityButtons.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } + return UIAccessibilityCustomRotor(name: "Buttons") { [weak self] predicate in + guard let self, let accessibilityButtons = self.accessibilityButtons else { return UIAccessibilityCustomRotorItemResult() } + if predicate.searchDirection == .next { + self.currentRotorIndex += 1 + if self.currentRotorIndex > accessibilityButtons.count { + self.currentRotorIndex = 1 + } + } else { + self.currentRotorIndex -= 1 + if self.currentRotorIndex <= 0 { + self.currentRotorIndex = accessibilityButtons.count + } + } + var rotorElement = accessibilityButtons[self.currentRotorIndex - 1] + if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { + tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) + rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) }.filter { ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id } as Any + } + UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) + return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) + } + } + private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/AccessibilityModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/AccessibilityModelProtocol.swift index a7508e39..809334d8 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/AccessibilityModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/AccessibilityModelProtocol.swift @@ -12,6 +12,9 @@ import Foundation public protocol AccessibilityModelProtocol { var accessibilityIdentifier: String? { get set } + var accessibilityTraits: UIAccessibilityTraits? { get set } + var accessibilityText: String? { get set } + var accessibilityValue: String? { get set } } public extension AccessibilityModelProtocol { @@ -20,4 +23,19 @@ public extension AccessibilityModelProtocol { get { nil } set { } } + + var accessibilityTraits: UIAccessibilityTraits? { + get { nil } + set { } + } + + var accessibilityText: String? { + get { nil } + set { } + } + + var accessibilityValue: String? { + get { nil } + set { } + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index dbd6b2df..3a6bc8f6 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -107,9 +107,9 @@ public protocol MoleculeViewModelProtocol: UIView { var moleculeModel: MoleculeModelProtocol? { get } } -extension MoleculeViewModelProtocol { +public extension MoleculeViewModelProtocol { - var moleculeModel: MoleculeModelProtocol? { + public var moleculeModel: MoleculeModelProtocol? { get { nil } } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 15ea1e03..7450b716 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -161,3 +161,8 @@ extension Button: AppleGuidelinesProtocol { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } } + +extension Button: MoleculeViewModelProtocol { + + public var moleculeModel: MoleculeModelProtocol? { model } +} From dad947d7c8c61e6ced6c777ca0255c4b51c8d6ea Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Mon, 25 Sep 2023 16:20:11 +0530 Subject: [PATCH 055/112] updated identifying buttons on view for rotor --- .../Accessibility/AccessibilityHandler.swift | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 83b0347a..d609aecd 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -224,6 +224,7 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) //To track FAB & HAB elements on UI + identifyAndPrepareForButtonRotor() guard let accessibilityElement = AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() else { return } if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { AccessibilityHandler.shared()?.previousAccessiblityElement = accessibilityElement @@ -234,24 +235,15 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { //MARK: - Private Methods private func identifyAndPrepareForButtonRotor() { - let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? UIViewController) - var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] - var currentIndexPath: IndexPath? - rotorElements = (currentViewController as? MoleculeListTemplate)?.templateModel?.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in - if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = (currentViewController as? MoleculeListTemplate)?.getIndexPath(for: listModel) { currentIndexPath = indexPath - } - var result = result - if (model.accessibilityTraits?.contains(.button) ?? false), let currentIndexPath { - result.append((model, currentIndexPath)) - } - return result - }) ?? [] + let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) var accessibilityButtons: [Any?]? = currentViewController?.navigationItem.leftBarButtonItems ?? [] accessibilityButtons?.append(contentsOf: currentViewController?.navigationItem.rightBarButtonItems ?? []) if let tabs = (currentViewController as? SubNavManagerController)?.tabs { accessibilityButtons?.append(contentsOf: tabs.subviews.filter { $0.accessibilityTraits.contains(.button) }) } - accessibilityButtons?.append(contentsOf: rotorElements) + if let rotorElements = getRotorButtonsBasedOn(template: currentViewController) { + accessibilityButtons?.append(contentsOf: rotorElements) + } if let tabBarHidden = (delegateObj?.moleculeDelegate as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { accessibilityButtons?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { $0.accessibilityTraits.contains(.button)}) } @@ -259,6 +251,29 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { currentViewController?.navigationController?.accessibilityCustomRotors = [createRotorForButtons()].compactMap { $0 } } + private func getRotorButtonsBasedOn(template: ViewController?) -> [Any]? { + if let currentViewController = template as? MoleculeListTemplate, let templateModel = currentViewController.templateModel { //List templates + var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] + var currentIndexPath: IndexPath? + rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in + if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = currentViewController.getIndexPath(for: listModel) { + currentIndexPath = indexPath + } + var result = result + if (model.accessibilityTraits?.contains(.button) ?? false), let currentIndexPath { + result.append((model, currentIndexPath)) + } + return result + }) + let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } as? [Any] ?? [] + let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } as? [Any] ?? [] + return headerViewElements + (rotorElements as [Any]) + footerViewElements + } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates + return currentViewController.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } + } + return nil + } + private func createRotorForButtons() -> UIAccessibilityCustomRotor? { guard let accessibilityButtons, !accessibilityButtons.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } return UIAccessibilityCustomRotor(name: "Buttons") { [weak self] predicate in @@ -277,7 +292,7 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { var rotorElement = accessibilityButtons[self.currentRotorIndex - 1] if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) }.filter { ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id } as Any + rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) }.filter { ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id }.first as Any } UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) From 00427f9e774ef5139132d35247fd4151a3f2e032 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Tue, 26 Sep 2023 00:15:20 +0530 Subject: [PATCH 056/112] changed to dynamic rotor --- MVMCoreUI.xcodeproj/project.pbxproj | 4 - .../Accessibility/AccessibilityHandler.swift | 126 ++++++++++++------ .../Templates/ModalMoleculeListTemplate.swift | 5 - .../MVMCoreUISession+Extension.swift | 22 --- MVMCoreUI/OtherHandlers/MVMCoreUISession.m | 1 - 5 files changed, 84 insertions(+), 74 deletions(-) delete mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index bb67be60..4e04dafc 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,7 +168,6 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; - 71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */; }; 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; @@ -756,7 +755,6 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; - 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUISession+Extension.swift"; sourceTree = ""; }; 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; @@ -2279,7 +2277,6 @@ D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */, D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */, D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */, - 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */, D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */, D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */, @@ -3096,7 +3093,6 @@ D29C559025C095210082E7D6 /* Video.swift in Sources */, D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */, AA104B1C24474A76004D2810 /* HeadersH2ButtonsModel.swift in Sources */, - 71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, AAE7270C24AC8B8500A3ED0E /* HeadersH2CaretLinkModel.swift in Sources */, BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */, diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index d609aecd..d25ab81e 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -191,23 +191,28 @@ open class AccessibilityHandler { // MARK: - Accessibility Handler Behaviour ///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. -struct AccessibilityHandlerBehaviorModel: PageBehaviorModelProtocol { +open class AccessibilityHandlerBehavior: PageVisibilityBehavior { - var shouldAllowMultipleInstances = false - static var identifier = "accessibilityHandlerBehaviorModel" -} - -class AccessibilityHandlerBehavior: PageVisibilityBehavior { + enum RotorType: String, CaseIterable { + + case button = "Buttons" + + var trait: UIAccessibilityTraits { + switch self { + case .button: + return .button + } + } + } + public var anyCancellable: Set = [] private var delegateObj: MVMCoreUIDelegateObject? - private var anyCancellable: Set = [] - private var accessibilityButtons: [Any]? private var currentRotorIndex: Int = 0 required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } //MARK: - PageVisibiltyBehaviour - public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { + open func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, (AccessibilityHandler.shared()?.canPostAccessbilityNotification(for: controller) ?? true), @@ -222,9 +227,9 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { ///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered. ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain - func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + open func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) //To track FAB & HAB elements on UI - identifyAndPrepareForButtonRotor() + identifyAndPrepareRotors() guard let accessibilityElement = AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() else { return } if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { AccessibilityHandler.shared()?.previousAccessiblityElement = accessibilityElement @@ -234,65 +239,101 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { } //MARK: - Private Methods - private func identifyAndPrepareForButtonRotor() { + private func identifyAndPrepareRotors() { + var rotorElements: [UIAccessibilityCustomRotor] = [] let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) - var accessibilityButtons: [Any?]? = currentViewController?.navigationItem.leftBarButtonItems ?? [] - accessibilityButtons?.append(contentsOf: currentViewController?.navigationItem.rightBarButtonItems ?? []) - if let tabs = (currentViewController as? SubNavManagerController)?.tabs { - accessibilityButtons?.append(contentsOf: tabs.subviews.filter { $0.accessibilityTraits.contains(.button) }) + for element in RotorType.allCases { + if let elements = getTraitMappedElements(template: currentViewController, type: element), + let rotor = createRotor(elements, for: element) { + rotorElements.append(rotor) + } } - if let rotorElements = getRotorButtonsBasedOn(template: currentViewController) { - accessibilityButtons?.append(contentsOf: rotorElements) - } - if let tabBarHidden = (delegateObj?.moleculeDelegate as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { - accessibilityButtons?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { $0.accessibilityTraits.contains(.button)}) - } - self.accessibilityButtons = accessibilityButtons?.compactMap { $0 } - currentViewController?.navigationController?.accessibilityCustomRotors = [createRotorForButtons()].compactMap { $0 } + currentViewController?.navigationController?.accessibilityCustomRotors = rotorElements } - private func getRotorButtonsBasedOn(template: ViewController?) -> [Any]? { - if let currentViewController = template as? MoleculeListTemplate, let templateModel = currentViewController.templateModel { //List templates + private func getTraitMappedElements(template: ViewController?, type: RotorType) -> [Any]? { + var accessibilityElements: [Any?]? = template?.navigationItem.leftBarButtonItems ?? [] + accessibilityElements?.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) + if let tabs = (template as? SubNavManagerController)?.tabs { + accessibilityElements?.append(contentsOf: tabs.subviews.filter { + $0.accessibilityTraits.contains(type.trait) + }) + } + if let rotorElements = getRotorElementsFrom(template: template, type: type) { + accessibilityElements?.append(contentsOf: rotorElements) + } + if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { + accessibilityElements?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { + $0.accessibilityTraits.contains(type.trait) + }) + } + return accessibilityElements?.compactMap { $0 } + } + + private func getRotorElementsFrom(template: ViewController?, type: RotorType) -> [Any]? { + if let currentViewController = template as? MoleculeListTemplate, + let templateModel = currentViewController.templateModel, + let tableView = currentViewController.tableView { //List templates + var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] var currentIndexPath: IndexPath? + rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = currentViewController.getIndexPath(for: listModel) { currentIndexPath = indexPath } var result = result - if (model.accessibilityTraits?.contains(.button) ?? false), let currentIndexPath { + if (model.accessibilityTraits?.contains(type.trait) ?? false), let currentIndexPath { result.append((model, currentIndexPath)) } return result }) - let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } as? [Any] ?? [] - let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } as? [Any] ?? [] - return headerViewElements + (rotorElements as [Any]) + footerViewElements + + let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { + (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + } as? [Any] ?? [] + + let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { + (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + } as? [Any] ?? [] + + let otherInteractiveElements = currentViewController.view?.getMoleculeViews(excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView] + .compactMap { $0 }) { (subView: MoleculeViewProtocol) in + subView.accessibilityTraits.contains(type.trait) + } as? [Any] ?? [] + + return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates - return currentViewController.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) } + return currentViewController.view?.getMoleculeViews { + (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + } } return nil } - private func createRotorForButtons() -> UIAccessibilityCustomRotor? { - guard let accessibilityButtons, !accessibilityButtons.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } - return UIAccessibilityCustomRotor(name: "Buttons") { [weak self] predicate in - guard let self, let accessibilityButtons = self.accessibilityButtons else { return UIAccessibilityCustomRotorItemResult() } + private func createRotor(_ elements: [Any], for type: RotorType) -> UIAccessibilityCustomRotor? { + guard elements.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } + return UIAccessibilityCustomRotor(name: type.rawValue) { [weak self] predicate in + guard let self else { return UIAccessibilityCustomRotorItemResult() } if predicate.searchDirection == .next { self.currentRotorIndex += 1 - if self.currentRotorIndex > accessibilityButtons.count { + if self.currentRotorIndex > elements.count { self.currentRotorIndex = 1 } } else { self.currentRotorIndex -= 1 if self.currentRotorIndex <= 0 { - self.currentRotorIndex = accessibilityButtons.count + self.currentRotorIndex = elements.count } } - var rotorElement = accessibilityButtons[self.currentRotorIndex - 1] + var rotorElement = elements[self.currentRotorIndex - 1] if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(.button) }.filter { ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id }.first as Any + rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { + (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + }.filter { + ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id + }.first as Any } UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) @@ -319,16 +360,17 @@ class AccessibilityHandlerBehavior: PageVisibilityBehavior { // MARK: - Helpers extension UIView { - private func getNestedSubviews() -> [T] { + private func getNestedSubviews(excludedViews: [UIView]? = nil) -> [T] { subviews.flatMap { subView -> [T] in + guard !(excludedViews?.contains(subView) ?? false) else { return [] } var result = subView.getNestedSubviews() as [T] if let view = subView as? T { result.append(view) } return result } } - func getMoleculeViews(filter: ((T) -> Bool)) -> [T] { - return getNestedSubviews().compactMap { + func getMoleculeViews(excludedViews: [UIView]? = nil, filter: ((T) -> Bool)) -> [T] { + return getNestedSubviews(excludedViews: excludedViews).compactMap { filter($0) ? $0 : nil } } diff --git a/MVMCoreUI/Atomic/Templates/ModalMoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/ModalMoleculeListTemplate.swift index 656d5086..8f8ca005 100644 --- a/MVMCoreUI/Atomic/Templates/ModalMoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ModalMoleculeListTemplate.swift @@ -35,9 +35,4 @@ open class ModalMoleculeListTemplate: MoleculeListTemplate { MVMCoreUIActionHandler.performActionUnstructured(with: closeAction, additionalData: nil, delegateObject: self.delegateObject()) }) } - - open override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - accessibilityElements = [closeButton as Any, tableView as Any] - } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift b/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift deleted file mode 100644 index 89aae858..00000000 --- a/MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// MVMCoreUISession.swift -// MVMCoreUI -// -// Created by Bandaru, Krishna Kishore on 16/09/23. -// Copyright © 2023 Verizon Wireless. All rights reserved. -// - -import Foundation - -@objc public extension MVMCoreUISession { - - @objc func applyGlobalMVMCoreUIBehaviors(to viewController: UIViewController) { - - guard var behaviorController = viewController as? PageBehaviorHandlerProtocol, let delegateObject = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() else { return } - - let accessibilityHandlerBehavior = AccessibilityHandlerBehavior(model: AccessibilityHandlerBehaviorModel(), delegateObject: (delegateObject as! MVMCoreUIDelegateObject)) - - let behaviors = behaviorController.behaviors ?? [] - behaviorController.behaviors = behaviors + [accessibilityHandlerBehavior] - } -} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m index 02ed8df2..e1ca231f 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m @@ -61,7 +61,6 @@ - (void)applyGlobalBehaviorsToController:(nonnull UIViewController *)viewController { // Allow extending frameworks to apply behaviors to add cross cutting concerns to the base controllers. - [self applyGlobalMVMCoreUIBehaviorsTo:viewController]; } @end From 6b2e29ae626e170d85b798e291097dd08957f0cb Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 27 Sep 2023 12:18:58 +0530 Subject: [PATCH 057/112] refactored code --- .../Accessibility/AccessibilityHandler.swift | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index d25ab81e..af9c13a5 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -179,11 +179,12 @@ open class AccessibilityHandler { stop = true } } - return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in + return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in guard let modelElement, let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel, moleculeModel.id == modelElement.id else { return false } + stop = true return true }.first } @@ -242,9 +243,9 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { private func identifyAndPrepareRotors() { var rotorElements: [UIAccessibilityCustomRotor] = [] let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) - for element in RotorType.allCases { - if let elements = getTraitMappedElements(template: currentViewController, type: element), - let rotor = createRotor(elements, for: element) { + for type in RotorType.allCases { + if let elements = getTraitMappedElements(template: currentViewController, type: type), + let rotor = createRotor(elements, for: type) { rotorElements.append(rotor) } } @@ -289,23 +290,23 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { return result }) - let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { - (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in + subView.accessibilityTraits.contains(type.trait) } as? [Any] ?? [] - let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { - (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in + subView.accessibilityTraits.contains(type.trait) } as? [Any] ?? [] let otherInteractiveElements = currentViewController.view?.getMoleculeViews(excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView] - .compactMap { $0 }) { (subView: MoleculeViewProtocol) in + .compactMap { $0 }) { (subView: MoleculeViewProtocol, _) in subView.accessibilityTraits.contains(type.trait) } as? [Any] ?? [] return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates - return currentViewController.view?.getMoleculeViews { - (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) + return currentViewController.view?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in + subView.accessibilityTraits.contains(type.trait) } } return nil @@ -329,10 +330,10 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { var rotorElement = elements[self.currentRotorIndex - 1] if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { - (subView: MoleculeViewProtocol) in subView.accessibilityTraits.contains(type.trait) - }.filter { - ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id + rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in + guard subView.accessibilityTraits.contains(type.trait), (subView as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id else { return false } + stop = true + return true }.first as Any } UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) @@ -369,9 +370,17 @@ extension UIView { } } - func getMoleculeViews(excludedViews: [UIView]? = nil, filter: ((T) -> Bool)) -> [T] { - return getNestedSubviews(excludedViews: excludedViews).compactMap { - filter($0) ? $0 : nil + func getMoleculeViews(excludedViews: [UIView]? = nil, filter: ((T, inout Bool) -> Bool)) -> [T] { + var stop = false + var results: [T] = [] + for element: T in getNestedSubviews(excludedViews: excludedViews) { + if filter(element, &stop) { + results.append(element) + } + if stop { + break + } } + return results } } From 14391978704da1a57a6ddc15a98b24c054cac5ce Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 27 Sep 2023 09:40:54 -0500 Subject: [PATCH 058/112] marked up Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift index 37344dad..b739baf7 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/BadgeModel.swift @@ -16,6 +16,10 @@ open class BadgeModel: MoleculeModelProtocol { public static var identifier: String = "badge" public var id: String = UUID().uuidString public var backgroundColor: Color? + + //-------------------------------------------------- + // MARK: - VDS Properties + //-------------------------------------------------- public var text: String = "" public var accessibilityText: String? public var maxWidth: CGFloat? From 46fb714d3495331b375db1e35b6b8c23cef3ba97 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 27 Sep 2023 20:53:50 +0530 Subject: [PATCH 059/112] added heading level rotor --- .../Accessibility/AccessibilityHandler.swift | 108 +++++++++++------- .../Atomic/Atoms/Buttons/ButtonModel.swift | 3 + .../Atomic/Atoms/Views/Label/Label.swift | 7 ++ .../Protocols/MoleculeViewProtocol.swift | 2 +- MVMCoreUI/BaseClasses/Button.swift | 4 + 5 files changed, 81 insertions(+), 43 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index af9c13a5..aeba4970 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -197,18 +197,32 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { enum RotorType: String, CaseIterable { case button = "Buttons" + case header = "Header" var trait: UIAccessibilityTraits { switch self { case .button: return .button + case .header: + return .header } } + + func getUIAccessibilityCustomRotor(itemSearch: @escaping UIAccessibilityCustomRotor.Search) -> UIAccessibilityCustomRotor? { + var accessibilityCustomRotor: UIAccessibilityCustomRotor? + switch self { + case .header: + accessibilityCustomRotor = UIAccessibilityCustomRotor(systemType: .heading, itemSearch: itemSearch) + default: + accessibilityCustomRotor = UIAccessibilityCustomRotor(name: rawValue, itemSearch: itemSearch) + } + return accessibilityCustomRotor + } } public var anyCancellable: Set = [] private var delegateObj: MVMCoreUIDelegateObject? - private var currentRotorIndex: Int = 0 + private var rotorIndexes: [RotorType: Int] = [:] required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } @@ -239,43 +253,66 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } } - //MARK: - Private Methods + //MARK: - Accessibility Methods + + private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { + var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] + if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { + accessibilityElements.append(managerController.navigationController) + accessibilityElements.append(managerController.tabs) + accessibilityElements.append(contentsOf: managerController.view.subviews) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } + } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { + accessibilityElements.append(controller.navigationController) + accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } + } + } + + //MARK: - Rotor Methods private func identifyAndPrepareRotors() { var rotorElements: [UIAccessibilityCustomRotor] = [] let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) for type in RotorType.allCases { - if let elements = getTraitMappedElements(template: currentViewController, type: type), - let rotor = createRotor(elements, for: type) { + if let elements = getTraitMappedElements(currentViewController, type: type), + let rotor = createRotor(elements, for: type) { rotorElements.append(rotor) } } currentViewController?.navigationController?.accessibilityCustomRotors = rotorElements } - private func getTraitMappedElements(template: ViewController?, type: RotorType) -> [Any]? { - var accessibilityElements: [Any?]? = template?.navigationItem.leftBarButtonItems ?? [] - accessibilityElements?.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) + private func getTraitMappedElements(_ template: ViewController?, type: RotorType) -> [Any]? { + var accessibilityElements: [Any?] = [] + switch type { + case .button: + accessibilityElements.append(contentsOf: template?.navigationItem.leftBarButtonItems ?? []) + accessibilityElements.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) + case .header: + accessibilityElements.append(template?.navigationItem.titleView) + } if let tabs = (template as? SubNavManagerController)?.tabs { - accessibilityElements?.append(contentsOf: tabs.subviews.filter { + accessibilityElements.append(contentsOf: tabs.subviews.filter { $0.accessibilityTraits.contains(type.trait) }) } if let rotorElements = getRotorElementsFrom(template: template, type: type) { - accessibilityElements?.append(contentsOf: rotorElements) + accessibilityElements.append(contentsOf: rotorElements) } if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { - accessibilityElements?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { + accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { $0.accessibilityTraits.contains(type.trait) }) } - return accessibilityElements?.compactMap { $0 } + return accessibilityElements.compactMap { $0 } } private func getRotorElementsFrom(template: ViewController?, type: RotorType) -> [Any]? { if let currentViewController = template as? MoleculeListTemplate, - let templateModel = currentViewController.templateModel, - let tableView = currentViewController.tableView { //List templates - + let templateModel = currentViewController.templateModel, + let tableView = currentViewController.tableView { //List templates var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] var currentIndexPath: IndexPath? @@ -301,7 +338,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { let otherInteractiveElements = currentViewController.view?.getMoleculeViews(excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView] .compactMap { $0 }) { (subView: MoleculeViewProtocol, _) in subView.accessibilityTraits.contains(type.trait) - } as? [Any] ?? [] + } as? [Any] ?? [] return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates @@ -313,22 +350,24 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } private func createRotor(_ elements: [Any], for type: RotorType) -> UIAccessibilityCustomRotor? { - guard elements.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } - return UIAccessibilityCustomRotor(name: type.rawValue) { [weak self] predicate in + guard !elements.isEmpty else { return nil } + return type.getUIAccessibilityCustomRotor { [weak self] predicate in guard let self else { return UIAccessibilityCustomRotorItemResult() } + var rotorIndex = self.rotorIndexes[type] ?? 0 if predicate.searchDirection == .next { - self.currentRotorIndex += 1 - if self.currentRotorIndex > elements.count { - self.currentRotorIndex = 1 + rotorIndex += 1 + if rotorIndex > elements.count { + rotorIndex = 1 } } else { - self.currentRotorIndex -= 1 - if self.currentRotorIndex <= 0 { - self.currentRotorIndex = elements.count + rotorIndex -= 1 + if rotorIndex <= 0 { + rotorIndex = elements.count } } - var rotorElement = elements[self.currentRotorIndex - 1] - if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { + var rotorElement = elements[rotorIndex - 1] + if let tableView = (self.delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView, + let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in guard subView.accessibilityTraits.contains(type.trait), (subView as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id else { return false } @@ -336,26 +375,11 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { return true }.first as Any } - UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) + self.rotorIndexes[type] = rotorIndex + AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: rotorElement) return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) } } - - private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { - var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] - if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { - accessibilityElements.append(managerController.navigationController) - accessibilityElements.append(managerController.tabs) - accessibilityElements.append(contentsOf: managerController.view.subviews) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { - accessibilityElements.append(controller.navigationController) - accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } - } } // MARK: - Helpers diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 08afefde..92b50d29 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -35,6 +35,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var size: Styler.Button.Size? = .standard public var groupName: String = "" public var inverted: Bool = false + public var accessibilityTraits: UIAccessibilityTraits? public lazy var enabledColors: FacadeElements = (fill: enabled_fillColor(), text: enabled_textColor(), @@ -195,6 +196,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case disabledTextColor case disabledBorderColor case width + case accessibilityTraits } //-------------------------------------------------- @@ -207,6 +209,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits) title = try typeContainer.decode(String.self, forKey: .title) action = try typeContainer.decodeModel(codingKey: .action) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 01614f31..119fc66b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -44,6 +44,7 @@ public typealias ActionBlock = () -> () public var shouldMaskWhileRecording: Bool = false + private var model: MoleculeModelProtocol? //------------------------------------------------------ // MARK: - Multi-Action Text //------------------------------------------------------ @@ -408,6 +409,7 @@ public typealias ActionBlock = () -> () attributedText = attributedString originalAttributedString = attributedText } + self.model = labelModel } @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -1027,3 +1029,8 @@ func validateAttribute(range: NSRange, in string: NSAttributedString, type: Stri return range } + +extension Label: MoleculeViewModelProtocol { + + public var moleculeModel: MoleculeModelProtocol? { model } +} diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 3a6bc8f6..1d7ebd03 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -109,7 +109,7 @@ public protocol MoleculeViewModelProtocol: UIView { public extension MoleculeViewModelProtocol { - public var moleculeModel: MoleculeModelProtocol? { + var moleculeModel: MoleculeModelProtocol? { get { nil } } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 7450b716..c744452e 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -107,6 +107,10 @@ public typealias ButtonAction = (Button) -> () isEnabled = model.enabled } + if let accessibilityTraits = model.accessibilityTraits { + self.accessibilityTraits = accessibilityTraits + } + guard let model = model as? ButtonModelProtocol else { return } set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) From 2d83b8a6ef3d71afb408d0c67eed3116ca99eb06 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Sep 2023 10:22:38 -0400 Subject: [PATCH 060/112] Guard in link code. --- MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index 86776016..5ca77679 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -10,7 +10,7 @@ import UIKit import VDSColorTokens import VDS -@objcMembers open class Link: VDS.TextLink, VDSMoleculeViewProtocol { +open class Link: VDS.TextLink, VDSMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Public Properties @@ -43,14 +43,13 @@ import VDS open override func updateAccessibility() { super.updateAccessibility() - if let viewModel { - if let accessibilityText = viewModel.accessibilityText { - self.accessibilityLabel = accessibilityText - } - - if let accessibilityIdentifier = viewModel.accessibilityIdentifier { - self.accessibilityIdentifier = accessibilityIdentifier - } + guard let viewModel = viewModel else { return } + if let accessibilityText = viewModel.accessibilityText { + self.accessibilityLabel = accessibilityText + } + + if let accessibilityIdentifier = viewModel.accessibilityIdentifier { + self.accessibilityIdentifier = accessibilityIdentifier } } From 4d4f7c334d6ebc357ce6222a5578c6107d1387da Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Sep 2023 19:41:06 -0400 Subject: [PATCH 061/112] Code review: Fix data list items and navigation bar. --- MVMCoreUI/Atomic/Atoms/Views/LineModel.swift | 24 +++++++++++---- .../List/ListProgressBarThin.swift | 9 ++++-- .../List/ListProgressBarThinModel.swift | 10 +++---- .../ListRightVariableTotalData.swift | 18 ++++++++---- .../ListRightVariableTotalDataModel.swift | 20 +++++++++---- .../UINavigationController+Extension.swift | 29 +++++++++---------- .../SubNav/SubNavManagerController.swift | 13 +++++---- 7 files changed, 77 insertions(+), 46 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index ecb39df0..a170c4b4 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -10,7 +10,7 @@ import UIKit import VDSColorTokens import VDS -@objcMembers public class LineModel: MoleculeModelProtocol, Invertable { +public class LineModel: MoleculeModelProtocol, Invertable { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- @@ -29,9 +29,23 @@ import VDS case between } + /** + The style of the line: + - secondary (VDS Secondary) + - primary (VDS Primary) + - standard (VDS Secondary) - deprecated + - thin (VDS Primar) - deprecated + - medium (VDS Primar) + - heavy (VDS Primar) + - none (hidden) + */ public enum Style: String, Codable { case secondary case primary + case standard + case thin + case medium + case heavy case none } @@ -43,7 +57,7 @@ import VDS public var id: String = UUID().uuidString public var backgroundColor: Color? - public var type: Style = .primary + public var type: Style = .secondary public var frequency: Frequency? = .allExceptTop public var inverted: Bool = false @@ -58,7 +72,7 @@ import VDS self.type = type } - public init(verticalLineOf type: Style, backgroundColor: Color? = nil) { + public init(verticalLineOf type: Style) { self.type = type } @@ -100,7 +114,7 @@ import VDS /// adding code to look for the old useVerticalLine or the new orientation if let useVerticalLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalLine) { orientation = useVerticalLine ? .vertical : .horizontal - }else if let orientation = try typeContainer.decodeIfPresent(VDS.Line.Orientation.self, forKey: .orientation) { + } else if let orientation = try typeContainer.decodeIfPresent(VDS.Line.Orientation.self, forKey: .orientation) { self.orientation = orientation } } @@ -112,6 +126,6 @@ import VDS try container.encode(type, forKey: .type) try container.encode(inverted, forKey: .inverted) try container.encodeIfPresent(frequency, forKey: .frequency) - try container.encodeIfPresent(orientation == .vertical, forKey: .useVerticalLine) + try container.encode(orientation == .vertical, forKey: .useVerticalLine) } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift index d9875068..e29ef00c 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -7,7 +7,7 @@ // -@objcMembers open class ListProgressBarThin: TableViewCell { +open class ListProgressBarThin: TableViewCell { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -15,7 +15,11 @@ public let progressBar = ProgressBar() public let leftHeadline = Label(fontStyle: .BoldBodySmall) public let leftBody = Label(fontStyle: .BoldBodySmall) - public let rightBar = Line() + public let rightBar: DataLine = { + var line = DataLine() + line.heightConstraint.constant = 2 + return line + }() public let rightLabel = Label(fontStyle: .BoldBodySmall) private let barStackItem: StackItem private let rightLabelStackItem: StackItem @@ -102,7 +106,6 @@ leftHeadline.styleB1(true) leftBody.styleB2(true) rightLabel.styleB2(true) - rightBar.setStyle(.primary) } //------------------------------------------------------ diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index d6787f52..0cc5f172 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -16,14 +16,14 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { public var progressBar: ProgressBarModel public var leftHeadline: LabelModel public var leftBody: LabelModel? - public var rightBar: LineModel + public var rightBar: DataLineModel public var rightLabel: LabelModel //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel? = nil, rightBar: LineModel, rightLabel: LabelModel) { + public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel? = nil, rightBar: DataLineModel, rightLabel: LabelModel) { self.progressBar = progressBar self.leftHeadline = leftHeadline self.leftBody = leftBody @@ -38,9 +38,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { override public func setDefaults() { super.setDefaults() - - rightBar.type = .primary - + if rightBar.backgroundColor == nil { rightBar.backgroundColor = Color(uiColor: .gray) } @@ -74,7 +72,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { progressBar = try typeContainer.decode(ProgressBarModel.self, forKey:.progressBar) leftHeadline = try typeContainer.decode(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody) - rightBar = try typeContainer.decode(LineModel.self, forKey: .rightBar) + rightBar = try typeContainer.decode(DataLineModel.self, forKey: .rightBar) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) try super.init(from: decoder) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift index 9845ea16..6dc1ffe2 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalData.swift @@ -6,15 +6,26 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // +open class DataLine: View { + + lazy var heightConstraint = heightAnchor.constraint(equalToConstant: 4) + lazy var widthConstraint = widthAnchor.constraint(equalToConstant: 20) + + open override func setupView() { + super.setupView() + heightConstraint.isActive = true + widthConstraint.isActive = true + } +} -@objcMembers open class ListRightVariableTotalData: TableViewCell { +open class ListRightVariableTotalData: TableViewCell { //----------------------------------------------------- // MARK: - Outlets //----------------------------------------------------- public let leftLabel = Label(fontStyle: .BoldBodySmall) public let rightLabel = Label(fontStyle: .RegularBodySmall) - public let bar = Line() + public let bar = DataLine() //----------------------------------------------------- // MARK: - Properties @@ -44,8 +55,6 @@ override open func setupView() { super.setupView() - bar.setStyle(.primary) - bar.widthAnchor.constraint(equalToConstant: 20).isActive = true rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) addMolecule(stack) stack.restack() @@ -74,7 +83,6 @@ super.reset() leftLabel.setFontStyle(.BoldBodySmall) rightLabel.setFontStyle(.RegularBodySmall) - bar.setStyle(.primary) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift index 53ede6e5..28a3a25c 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableTotalDataModel.swift @@ -6,8 +6,18 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // +public struct DataLineModel: Codable, MoleculeModelProtocol { + public var id: String = UUID().uuidString + public static var identifier: String = "line" + public var backgroundColor: Color? + + private enum CodingKeys: String, CodingKey { + case backgroundColor + } +} public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -15,7 +25,7 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc public static var identifier: String = "listRVLine" public var leftLabel: LabelModel public var rightLabel: LabelModel - public var bar: LineModel + public var bar: DataLineModel //-------------------------------------------------- // MARK: - Method @@ -24,8 +34,6 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc override public func setDefaults() { super.setDefaults() rightLabel.hero = 0 - bar.type = .primary - if bar.backgroundColor == nil { bar.backgroundColor = Color(uiColor: .mvmBlue) } @@ -35,7 +43,7 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc // MARK: - Initializer //-------------------------------------------------- - public init(leftLabel: LabelModel, rightlabel:LabelModel, bar: LineModel){ + public init(leftLabel: LabelModel, rightlabel:LabelModel, bar: DataLineModel) { self.leftLabel = leftLabel self.rightLabel = rightlabel self.bar = bar @@ -46,7 +54,7 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc // MARK: - Keys //-------------------------------------------------- - private enum CodingKeys: String, CodingKey{ + private enum CodingKeys: String, CodingKey { case moleculeName case leftLabel case rightLabel @@ -61,7 +69,7 @@ public class ListRightVariableTotalDataModel: ListItemModel, MoleculeModelProtoc let typeContainer = try decoder.container(keyedBy: CodingKeys.self) leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) - bar = try typeContainer.decode(LineModel.self, forKey: .bar) + bar = try typeContainer.decode(DataLineModel.self, forKey: .bar) try super.init(from: decoder) } diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift index ac9684ac..fb1bde79 100644 --- a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -82,13 +82,6 @@ public extension UINavigationController { } } - /// Returns a ShadowImage based on the line property of NavigationItemModelProtocol - func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? { - guard let model = navigationItemModel.line else { return nil } - let line = Line(model: model, nil, nil) - return line.lineColor.image(CGSize(width: line.lineWidth, height: line.lineWidth)) - } - /// Convenience function for setting the navigation bar ui @MainActor func setNavigationBarUI(with model: NavigationItemModelProtocol) { @@ -105,14 +98,7 @@ public extension UINavigationController { appearance.backgroundColor = backgroundColor appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor) appearance.titlePositionAdjustment = model.titleOffset ?? .zero - if let type = model.line?.type, - type != .none, - let color = model.line?.backgroundColor { - appearance.shadowColor = color.uiColor - } else { - appearance.shadowColor = .clear - } - appearance.shadowImage = getNavigationBarShadowImage(for: model)?.withRenderingMode(.alwaysTemplate) + appearance.setShadow(for: model.line) navigationBar.standardAppearance = appearance navigationBar.scrollEdgeAppearance = appearance @@ -126,3 +112,16 @@ public extension UINavigationController { return viewController } } + +public extension UINavigationBarAppearance { + func setShadow(for model: LineModel?) { + let model = model ?? LineModel(type: .secondary) + let line = Line(model: model, nil, nil) + if model.type != .none { + shadowColor = line.lineColor + } else { + shadowColor = .clear + } + shadowImage = line.lineColor.image(CGSize(width: line.lineWidth, height: line.lineWidth)).withRenderingMode(.alwaysTemplate) + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index 458c0169..7461c93d 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -125,13 +125,14 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, /// Hides/Shows the navigation bar for the page. open func hideNavigationBarLine(_ isHidden: Bool) { guard self == navigationController?.topViewController else { return } - var color = UIColor.clear - if !isHidden, - let backgroundColor = (getCurrentViewController() as? PageProtocol)?.pageModel?.navigationBar?.line?.backgroundColor?.uiColor { - color = backgroundColor + var model: LineModel? + if isHidden { + model = LineModel(type: .none) + } else if let lineModel = (getCurrentViewController() as? PageProtocol)?.pageModel?.navigationBar?.line { + model = lineModel } - navigationController?.navigationBar.standardAppearance.shadowColor = color - navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = color + navigationController?.navigationBar.standardAppearance.setShadow(for: model) + navigationController?.navigationBar.scrollEdgeAppearance?.setShadow(for: model) } open override func updateViews() { From 1da8a6db77b106baf683f625ab6e4155e7ca065f Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Sep 2023 20:12:36 -0400 Subject: [PATCH 062/112] Default to secondary --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 6 +++--- .../UINavigationController+Extension.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index c3ce3d7f..2d20d209 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -28,17 +28,17 @@ import VDS public required init() { super.init() - viewModel = LineModel(type: .primary) + viewModel = LineModel(type: .secondary) } public override init(frame: CGRect) { super.init(frame: frame) - viewModel = LineModel(type: .primary) + viewModel = LineModel(type: .secondary) } public required init?(coder: NSCoder) { super.init(coder: coder) - viewModel = LineModel(type: .primary) + viewModel = LineModel(type: .secondary) } //-------------------------------------------------- diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift index fb1bde79..57aeb02d 100644 --- a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -117,7 +117,7 @@ public extension UINavigationBarAppearance { func setShadow(for model: LineModel?) { let model = model ?? LineModel(type: .secondary) let line = Line(model: model, nil, nil) - if model.type != .none { + if line.shouldBeVisible() { shadowColor = line.lineColor } else { shadowColor = .clear From e5e17a286df55c0bf4a9ef43c9ca0e3ff2ae61e5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Sep 2023 20:31:25 -0400 Subject: [PATCH 063/112] fix to vertical lines --- MVMCoreUI/Atomic/Atoms/Views/LineModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index a170c4b4..8a473016 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -74,6 +74,7 @@ public class LineModel: MoleculeModelProtocol, Invertable { public init(verticalLineOf type: Style) { self.type = type + orientation = .vertical } //-------------------------------------------------- From c3503edecbaf54888b915a29002f21a17ff92baa Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Sep 2023 20:39:50 -0400 Subject: [PATCH 064/112] Changes to ensure line does not stretch --- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index 2d20d209..ba0af74f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -17,6 +17,22 @@ import VDS open var viewModel: LineModel! open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? + + open override var orientation: Line.Orientation { + didSet { + if orientation == .horizontal { + setContentHuggingPriority(.defaultLow, for: .horizontal) + setContentHuggingPriority(.required, for: .vertical) + setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + setContentCompressionResistancePriority(.required, for: .vertical) + } else { + setContentHuggingPriority(.required, for: .horizontal) + setContentHuggingPriority(.defaultLow, for: .vertical) + setContentCompressionResistancePriority(.required, for: .horizontal) + setContentCompressionResistancePriority(.defaultLow, for: .vertical) + } + } + } //-------------------------------------------------- // MARK: - Initializer From 88505e704cab9b6ef93283311d34ad1e0597ffb1 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 4 Oct 2023 20:08:46 +0530 Subject: [PATCH 065/112] addressed review comments --- .../Accessibility/AccessibilityHandler.swift | 136 +++++------------- .../Atomic/Atoms/Selectors/ToggleModel.swift | 1 - .../Utility/MVMCoreUIUtility+Extension.swift | 7 +- 3 files changed, 43 insertions(+), 101 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index aeba4970..c77137a6 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -15,7 +15,6 @@ public class AccessbilityOperation: MVMCoreOperation { private let argument: Any? private let notificationType: UIAccessibility.Notification - private var timerSource: DispatchSourceTimer? public init(notificationType: UIAccessibility.Notification, argument: Any?) { self.notificationType = notificationType @@ -27,24 +26,18 @@ public class AccessbilityOperation: MVMCoreOperation { stop() return } - timerSource = DispatchSource.makeTimerSource() - timerSource?.setEventHandler { - Task { @MainActor [weak self] in - guard let self = self, !self.isCancelled else { - self?.stop() - return - } - UIAccessibility.post(notification: self.notificationType, argument: self.argument) - self.markAsFinished() + Task { @MainActor [weak self] in + guard let self = self, !self.isCancelled else { + self?.stop() + return } + UIAccessibility.post(notification: self.notificationType, argument: self.argument) + self.markAsFinished() } - timerSource?.schedule(deadline: .now()) - timerSource?.activate() } public func stop() { guard isCancelled else { return } - timerSource?.cancel() markAsFinished() } } @@ -65,26 +58,15 @@ open class AccessibilityHandler { queue.maxConcurrentOperationCount = 1 return queue }() - private(set) var accessibilityId: String? - private var announcementText: String? - private(set) var hasTopNotitificationInPage: Bool = false + public var accessibilityId: String? + public var hasTopNotificationInPage: Bool = false public init() { - registerWithResponseLoaded() registerForPageChanges() registerForFocusChanges() } // MARK: - Register with Accessibility Handler listeners - /// Registers with the notification center to know when json is updated and to capture previous accessbility focused id & announcment text - private func registerWithResponseLoaded() { - NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded)) - .sink { [weak self] notification in - self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId") - self?.announcementText = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("announcementText") - }.store(in: &anyCancellable) - } - private func registerForFocusChanges() { //Since foucs shifted to other elements cancelling existing focus shift notifications if any NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) @@ -96,12 +78,14 @@ open class AccessibilityHandler { func registerForTopNotificationsChanges() { NotificationHandler.shared()?.onNotificationWillShow .sink { [weak self] (_, model) in - self?.hasTopNotitificationInPage = true - self?.capturePreviousFocusElement() + if self?.previousAccessiblityElement == nil { + self?.capturePreviousFocusElement() + } }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationShown .sink { [weak self] (view, model) in self?.post(notification: .layoutChanged, argument: view) + self?.hasTopNotificationInPage = false }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationWillDismiss .sink { [weak self] (view, model) in @@ -129,10 +113,7 @@ open class AccessibilityHandler { private func willNavigate(_ operation: NavigationOperation) { previousAccessiblityElement = nil - if let announcementText { - post(notification: .announcement, argument: announcementText) - } - if let subNavManagerController = operation.toNavigationControllerViewControllers?.last as? SubNavManagerController { + if let subNavManagerController = (operation.toNavigationControllerViewControllers?.last as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { delegate = subNavManagerController.getCurrentViewController() as? MVMCoreViewControllerProtocol } else { delegate = operation.toNavigationControllerViewControllers?.last as? MVMCoreViewControllerProtocol @@ -164,35 +145,23 @@ open class AccessibilityHandler { //To get first focus element on the screen open func getFirstFocusedElementOnScreen() -> Any? { - (delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar + (delegate as? UIViewController)?.navigationController?.navigationBar } //Subclass can decide to trigger Accessibility notification on screen change. open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } func getPreDefinedFocusedElementIfAny() -> UIView? { - guard let accessibilityId else { return nil } - var modelElement: MoleculeModelProtocol? - ((delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate)?.getRootMolecules().depthFirstTraverse(options: .leafNodesOnly, depth: 0) { index, model, stop in - if model.id == accessibilityId { - modelElement = model - stop = true - } + guard let accessibilityId, let view = (delegate as? UIViewController)?.view else { return nil } + return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [view]).first { + ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == accessibilityId } - return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in - guard let modelElement, let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel, - moleculeModel.id == modelElement.id else { - return false - } - stop = true - return true - }.first } } // MARK: - Accessibility Handler Behaviour ///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. -open class AccessibilityHandlerBehavior: PageVisibilityBehavior { +open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTransformationBehavior { enum RotorType: String, CaseIterable { @@ -226,13 +195,23 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + guard let loadObject = (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.loadObject else { return } + AccessibilityHandler.shared()?.accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId") + //TODO: - Need to revisit this logic + AccessibilityHandler.shared()?.hasTopNotificationInPage = loadObject?.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || loadObject?.responseInfoMap?.optionalStringForKey("userMessage") != nil + if let announcementText = loadObject?.pageJSON?.optionalStringForKey("announcementText") { + AccessibilityHandler.shared()?.post(notification: .announcement, argument: announcementText) + } + } + //MARK: - PageVisibiltyBehaviour open func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, (AccessibilityHandler.shared()?.canPostAccessbilityNotification(for: controller) ?? true), AccessibilityHandler.shared()?.accessibilityId == nil else { return } - if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { + if AccessibilityHandler.shared()?.hasTopNotificationInPage ?? false { AccessibilityHandler.shared()?.previousAccessiblityElement = AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen() } else { AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen()) @@ -246,7 +225,8 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { updateAccessibilityViews(delegateObject) //To track FAB & HAB elements on UI identifyAndPrepareRotors() guard let accessibilityElement = AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() else { return } - if AccessibilityHandler.shared()?.hasTopNotitificationInPage ?? false { + AccessibilityHandler.shared()?.accessibilityId = nil + if AccessibilityHandler.shared()?.hasTopNotificationInPage ?? false { AccessibilityHandler.shared()?.previousAccessiblityElement = accessibilityElement } else { AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: accessibilityElement) @@ -254,8 +234,8 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } //MARK: - Accessibility Methods - private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { + //TODO: - Need to revisit this logic var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { accessibilityElements.append(managerController.navigationController) @@ -313,7 +293,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { if let currentViewController = template as? MoleculeListTemplate, let templateModel = currentViewController.templateModel, let tableView = currentViewController.tableView { //List templates - var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] + var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] ///Identifying the trait mapped elements models var currentIndexPath: IndexPath? rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in @@ -327,24 +307,15 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { return result }) - let headerViewElements = currentViewController.tableView.tableHeaderView?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in - subView.accessibilityTraits.contains(type.trait) - } as? [Any] ?? [] + let headerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - let footerViewElements = currentViewController.tableView.tableFooterView?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in - subView.accessibilityTraits.contains(type.trait) - } as? [Any] ?? [] + let footerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableFooterView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - let otherInteractiveElements = currentViewController.view?.getMoleculeViews(excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView] - .compactMap { $0 }) { (subView: MoleculeViewProtocol, _) in - subView.accessibilityTraits.contains(type.trait) - } as? [Any] ?? [] + let otherInteractiveElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }, excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates - return currentViewController.view?.getMoleculeViews { (subView: MoleculeViewProtocol, _) in - subView.accessibilityTraits.contains(type.trait) - } + return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] } return nil } @@ -369,11 +340,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { if let tableView = (self.delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView, let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in - guard subView.accessibilityTraits.contains(type.trait), (subView as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id else { return false } - stop = true - return true - }.first as Any + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [tableView.cellForRow(at: element.indexPath)].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) && ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id }.first as Any } self.rotorIndexes[type] = rotorIndex AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: rotorElement) @@ -381,30 +348,3 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } } } - -// MARK: - Helpers -extension UIView { - - private func getNestedSubviews(excludedViews: [UIView]? = nil) -> [T] { - subviews.flatMap { subView -> [T] in - guard !(excludedViews?.contains(subView) ?? false) else { return [] } - var result = subView.getNestedSubviews() as [T] - if let view = subView as? T { result.append(view) } - return result - } - } - - func getMoleculeViews(excludedViews: [UIView]? = nil, filter: ((T, inout Bool) -> Bool)) -> [T] { - var stop = false - var results: [T] = [] - for element: T in getNestedSubviews(excludedViews: excludedViews) { - if filter(element, &stop) { - results.append(element) - } - if stop { - break - } - } - return results - } -} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index fc267cff..008c2911 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -151,6 +151,5 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Identifiable try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encode(readOnly, forKey: .readOnly) - try container.encodeIfPresent(id, forKey: .id) } } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift index e04de6bb..16677c7f 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift +++ b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift @@ -39,7 +39,7 @@ public extension MVMCoreUIUtility { /// - type: The type you are looking for. /// - views: The starting array of subviews. /// - Returns: Will return an array of any view associated with the given type. Will return an empty array of none were found. - static func findViews(by type: T.Type, views: [UIView]) -> [T] { + static func findViews(by type: T.Type, views: [UIView], excludedViews: [UIView] = []) -> [T] { guard !views.isEmpty else { return [] } @@ -47,6 +47,9 @@ public extension MVMCoreUIUtility { var matching = [T]() for view in views { + guard !excludedViews.contains(view) else { + continue + } if view is T { matching.append(view as! T) } @@ -54,7 +57,7 @@ public extension MVMCoreUIUtility { queue.append(contentsOf: view.subviews) } - return findViews(by: type, views: queue) + matching + return findViews(by: type, views: queue, excludedViews: excludedViews) + matching } static func visibleNavigationBarStlye() -> NavigationItemStyle? { From 8dc475ffdd7ca2cd1fbb6c019eb403b12befcd36 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 11 Oct 2023 00:47:31 +0530 Subject: [PATCH 066/112] addressed review comments & added model property to MoleculeViewProtocol --- .../Accessibility/AccessibilityHandler.swift | 322 +++++++++--------- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 5 - .../Atomic/Atoms/Views/Label/Label.swift | 7 +- .../Atomic/Atoms/Views/ProgressBar.swift | 7 +- MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift | 8 +- .../HorizontalCombinationViews/TabBar.swift | 16 +- .../Protocols/MoleculeViewProtocol.swift | 19 +- MVMCoreUI/BaseClasses/Button.swift | 5 - .../NavigationController.swift | 5 + ...MCoreUISplitViewController+Extension.swift | 5 + .../SubNav/SubNavManagerController.swift | 4 + 11 files changed, 205 insertions(+), 198 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index c77137a6..f2bfd397 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -32,7 +32,13 @@ public class AccessbilityOperation: MVMCoreOperation { return } UIAccessibility.post(notification: self.notificationType, argument: self.argument) - self.markAsFinished() + if self.notificationType == .announcement { + NotificationCenter.default.addObserver(forName: UIAccessibility.announcementDidFinishNotification, object: nil, queue: .main) { _ in + self.markAsFinished() + } + } else { + self.markAsFinished() + } } } @@ -44,125 +50,6 @@ public class AccessbilityOperation: MVMCoreOperation { open class AccessibilityHandler { - public static func shared() -> Self? { - guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } - return MVMCoreActionUtility.fatalClassCheck(object: shared) - } - - public var previousAccessiblityElement: Any? - public var anyCancellable: Set = [] - public weak var delegate: MVMCoreViewControllerProtocol? - - private var accessibilityOperationQueue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - return queue - }() - public var accessibilityId: String? - public var hasTopNotificationInPage: Bool = false - - public init() { - registerForPageChanges() - registerForFocusChanges() - } - - // MARK: - Register with Accessibility Handler listeners - private func registerForFocusChanges() { - //Since foucs shifted to other elements cancelling existing focus shift notifications if any - NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) - .sink { [weak self] _ in - self?.cancelAllOperations() - }.store(in: &anyCancellable) - } - - func registerForTopNotificationsChanges() { - NotificationHandler.shared()?.onNotificationWillShow - .sink { [weak self] (_, model) in - if self?.previousAccessiblityElement == nil { - self?.capturePreviousFocusElement() - } - }.store(in: &anyCancellable) - NotificationHandler.shared()?.onNotificationShown - .sink { [weak self] (view, model) in - self?.post(notification: .layoutChanged, argument: view) - self?.hasTopNotificationInPage = false - }.store(in: &anyCancellable) - NotificationHandler.shared()?.onNotificationWillDismiss - .sink { [weak self] (view, model) in - self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed")) - }.store(in: &anyCancellable) - NotificationHandler.shared()?.onNotificationDismissed - .sink { [weak self] (view, model) in - self?.postAccessbilityToPrevElement() - }.store(in: &anyCancellable) - } - - /// Registers to know when pages change. - open func registerForPageChanges() { - NavigationHandler.shared() - .onNavigation - .sink { [self] (event, operation) in - switch event { - case .willNavigate: - willNavigate(operation) - default: - break - } - }.store(in: &anyCancellable) - } - - private func willNavigate(_ operation: NavigationOperation) { - previousAccessiblityElement = nil - if let subNavManagerController = (operation.toNavigationControllerViewControllers?.last as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { - delegate = subNavManagerController.getCurrentViewController() as? MVMCoreViewControllerProtocol - } else { - delegate = operation.toNavigationControllerViewControllers?.last as? MVMCoreViewControllerProtocol - } - } - - // MARK: - Accessibility Handler operation events - open func capturePreviousFocusElement() { - previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) - } - - open func postAccessbilityToPrevElement() { - post(notification: .layoutChanged, argument: previousAccessiblityElement) - } - - private func add(operation: Operation) { - accessibilityOperationQueue.addOperation(operation) - } - - private func cancelAllOperations() { - accessibilityOperationQueue.cancelAllOperations() - } - - public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) { - guard UIAccessibility.isVoiceOverRunning else { return } - let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) - add(operation: accessbilityOperation) - } - - //To get first focus element on the screen - open func getFirstFocusedElementOnScreen() -> Any? { - (delegate as? UIViewController)?.navigationController?.navigationBar - } - - //Subclass can decide to trigger Accessibility notification on screen change. - open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } - - func getPreDefinedFocusedElementIfAny() -> UIView? { - guard let accessibilityId, let view = (delegate as? UIViewController)?.view else { return nil } - return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [view]).first { - ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == accessibilityId - } - } -} - -// MARK: - Accessibility Handler Behaviour -///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. -open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTransformationBehavior { - enum RotorType: String, CaseIterable { case button = "Buttons" @@ -189,72 +76,121 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTra } } - public var anyCancellable: Set = [] - private var delegateObj: MVMCoreUIDelegateObject? - private var rotorIndexes: [RotorType: Int] = [:] + public static func shared() -> Self? { + guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } + return MVMCoreActionUtility.fatalClassCheck(object: shared) + } - required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } + public var accessibilityId: String? + public var previousAccessiblityElement: Any? + public var anyCancellable: Set = [] + public weak var delegate: MVMCoreViewControllerProtocol? + private var rotorIndexes: [RotorType: Int] = [:] + private var hasTopNotificationInPage: Bool { NotificationHandler.shared()?.isNotificationShowing() ?? false } + private let accessibilityOperationQueue: OperationQueue = { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + return queue + }() + + public init() { + registerForFocusChanges() + } + + // MARK: - Accessibility Handler operation events + open func capturePreviousFocusElement() { + previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) + } + + open func postAccessbilityToPrevElement() { + post(notification: .layoutChanged, argument: previousAccessiblityElement) + previousAccessiblityElement = nil + } + + public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) { + guard UIAccessibility.isVoiceOverRunning else { return } + let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) + accessibilityOperationQueue.addOperation(accessbilityOperation) + } + + //To get first focus element on the screen + open func getFirstFocusedElementOnScreen() -> Any? { + (delegate as? UIViewController)?.navigationController?.navigationBar + } + + //Subclass can decide to trigger Accessibility notification on screen change. + open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } + + func getPreDefinedFocusedElementIfAny() -> UIView? { + guard let accessibilityId, let view = (delegate as? UIViewController)?.view else { return nil } + return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [view]).first { + $0.model?.id == accessibilityId + } + } +} + +extension AccessibilityHandler { public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + rotorIndexes = [:] + previousAccessiblityElement = nil guard let loadObject = (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.loadObject else { return } - AccessibilityHandler.shared()?.accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId") - //TODO: - Need to revisit this logic - AccessibilityHandler.shared()?.hasTopNotificationInPage = loadObject?.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || loadObject?.responseInfoMap?.optionalStringForKey("userMessage") != nil + accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId") if let announcementText = loadObject?.pageJSON?.optionalStringForKey("announcementText") { - AccessibilityHandler.shared()?.post(notification: .announcement, argument: announcementText) + post(notification: .announcement, argument: announcementText) } + delegate = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } //MARK: - PageVisibiltyBehaviour - open func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { + public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, - (AccessibilityHandler.shared()?.canPostAccessbilityNotification(for: controller) ?? true), - AccessibilityHandler.shared()?.accessibilityId == nil else { return } - if AccessibilityHandler.shared()?.hasTopNotificationInPage ?? false { - AccessibilityHandler.shared()?.previousAccessiblityElement = AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen() + canPostAccessbilityNotification(for: controller), + accessibilityId == nil else { return } + if hasTopNotificationInPage { + previousAccessiblityElement = getFirstFocusedElementOnScreen() } else { - AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen()) + post(notification: .layoutChanged, argument: getFirstFocusedElementOnScreen()) } - delegateObj = delegateObject } ///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered. ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain - open func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { - updateAccessibilityViews(delegateObject) //To track FAB & HAB elements on UI - identifyAndPrepareRotors() - guard let accessibilityElement = AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() else { return } - AccessibilityHandler.shared()?.accessibilityId = nil - if AccessibilityHandler.shared()?.hasTopNotificationInPage ?? false { - AccessibilityHandler.shared()?.previousAccessiblityElement = accessibilityElement + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + identifyAndPrepareRotors(delegateObject) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + (delegateObject?.moleculeDelegate as? UIViewController)?.view.accessibilityElements = nil + } + guard let accessibilityElement = getPreDefinedFocusedElementIfAny() else { return } + accessibilityId = nil + if hasTopNotificationInPage { + previousAccessiblityElement = accessibilityElement } else { - AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: accessibilityElement) + post(notification: .layoutChanged, argument: accessibilityElement) } } //MARK: - Accessibility Methods private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { - //TODO: - Need to revisit this logic - var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] - if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { - accessibilityElements.append(managerController.navigationController) - accessibilityElements.append(managerController.tabs) - accessibilityElements.append(contentsOf: managerController.view.subviews) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { - accessibilityElements.append(controller.navigationController) - accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } + var currentController = delegateObject?.moleculeDelegate as? UIViewController + var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController] + if let manager = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? MVMCoreViewManagerProtocol & UIViewController), + let managerAccessibilityElements = manager.getAccessibilityElements() { + accessibilityElements.append(contentsOf: managerAccessibilityElements) + accessibilityElements.append(contentsOf: manager.view.subviews) + currentController = manager + } else { + accessibilityElements.append(contentsOf: currentController?.view.subviews ?? []) } + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + currentController?.view.accessibilityElements = accessibilityElements.compactMap { $0 } } - + //MARK: - Rotor Methods - private func identifyAndPrepareRotors() { + private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) { var rotorElements: [UIAccessibilityCustomRotor] = [] - let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) + let currentViewController = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObject?.moleculeDelegate as? ViewController) for type in RotorType.allCases { if let elements = getTraitMappedElements(currentViewController, type: type), let rotor = createRotor(elements, for: type) { @@ -337,14 +273,76 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTra } } var rotorElement = elements[rotorIndex - 1] - if let tableView = (self.delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView, + if let tableView = (self.delegate as? MoleculeListTemplate)?.tableView, let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [tableView.cellForRow(at: element.indexPath)].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) && ($0 as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id }.first as Any + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [tableView.cellForRow(at: element.indexPath)].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any } self.rotorIndexes[type] = rotorIndex - AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: rotorElement) + post(notification: .layoutChanged, argument: rotorElement) return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) } } } + +@objc extension AccessibilityHandler { + + // MARK: - Register with Accessibility Handler listeners + private func registerForFocusChanges() { + //Since focus shifted to other elements cancelling existing focus shift notifications if any + NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) + .sink { [weak self] _ in + self?.accessibilityOperationQueue.cancelAllOperations() + }.store(in: &anyCancellable) + } + + func registerForTopNotificationsChanges() { + NotificationHandler.shared()?.onNotificationWillShow + .sink { [weak self] (_, model) in + if self?.previousAccessiblityElement == nil { + self?.capturePreviousFocusElement() + } + }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationShown + .sink { [weak self] (view, model) in + self?.post(notification: .layoutChanged, argument: view) + }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationWillDismiss + .sink { [weak self] (view, model) in + self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"), priority: .veryHigh) + }.store(in: &anyCancellable) + NotificationHandler.shared()?.onNotificationDismissed + .sink { [weak self] (view, model) in + self?.postAccessbilityToPrevElement() + }.store(in: &anyCancellable) + } +} + +// MARK: - Accessibility Handler Behaviour +///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. +open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTransformationBehavior { + + public let accessibilityHandler: AccessibilityHandler? + + public init(accessibilityHandler: AccessibilityHandler?) { + self.accessibilityHandler = accessibilityHandler + } + + required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + accessibilityHandler = AccessibilityHandler.shared() //Protocol Mandatory init method. + } + + //MARK: - PageMoleculeTransformationBehavior + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + accessibilityHandler?.onPageNew(rootMolecules: rootMolecules, delegateObject) + } + + //MARK: - PageVisibiltyBehaviour + open func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { + accessibilityHandler?.willShowPage(delegateObject) + } + + open func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + accessibilityHandler?.onPageShown(delegateObject) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index d84efad6..4341614e 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -418,8 +418,3 @@ extension Toggle { public func horizontalAlignment() -> UIStackView.Alignment { .trailing } } - -extension Toggle: MoleculeViewModelProtocol { - - public var moleculeModel: MoleculeModelProtocol? { model } -} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 119fc66b..9af907e1 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -44,7 +44,7 @@ public typealias ActionBlock = () -> () public var shouldMaskWhileRecording: Bool = false - private var model: MoleculeModelProtocol? + public var model: MoleculeModelProtocol? //------------------------------------------------------ // MARK: - Multi-Action Text //------------------------------------------------------ @@ -1029,8 +1029,3 @@ func validateAttribute(range: NSRange, in string: NSAttributedString, type: Stri return range } - -extension Label: MoleculeViewModelProtocol { - - public var moleculeModel: MoleculeModelProtocol? { model } -} diff --git a/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift index a9fde5c1..a68cdc99 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift @@ -13,8 +13,11 @@ import Foundation //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - var progressBarModel: ProgressBarModel? + public var model: MoleculeModelProtocol? + public var progressBarModel: ProgressBarModel? { + get { model as? ProgressBarModel } + set { model = newValue } + } var thickness: CGFloat = 8.0 { willSet(newValue) { diff --git a/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift b/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift index 41d68493..55d83e75 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Tilelet.swift @@ -18,7 +18,13 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{ //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var viewModel: TileletModel! + + public var model: MoleculeModelProtocol? + + public var viewModel: TileletModel! { + get { model as? TileletModel } + set { model = newValue } + } public var delegateObject: MVMCoreUIDelegateObject? public var additionalData: [AnyHashable: Any]? diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index e49006fa..ebc7f469 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -9,7 +9,13 @@ import VDSColorTokens @objcMembers open class TabBar: UITabBar, MoleculeViewProtocol, TabBarProtocol, UITabBarDelegate { - public var model: TabBarModel + public var model: MoleculeModelProtocol? + + public var tabModel: TabBarModel { + get { model as! TabBarModel } + set { model = newValue } + } + public var delegateObject: MVMCoreUIDelegateObject? public let line = Line() @@ -68,8 +74,8 @@ import VDSColorTokens // MARK: - UITabBarDelegate public func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { - model.selectedTab = item.tag - let action = model.tabs[item.tag].action + tabModel.selectedTab = item.tag + let action = tabModel.tabs[item.tag].action Task(priority: .userInitiated) { try await Button.performButtonAction(with: action, button: item, delegateObject: delegateObject, additionalData: nil) } @@ -79,7 +85,7 @@ import VDSColorTokens public func highlightTab(at index: Int) { MVMCoreDispatchUtility.performBlock(onMainThread: { guard let newSelectedItem = self.items?[index] else { return } - self.model.selectedTab = index + self.tabModel.selectedTab = index self.selectedItem = newSelectedItem }) } @@ -92,7 +98,7 @@ import VDSColorTokens }) } - public func currentTabIndex() -> Int { model.selectedTab } + public func currentTabIndex() -> Int { tabModel.selectedTab } } extension UITabBarItem: MFButtonProtocol { } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 1d7ebd03..7dd4fa26 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -12,6 +12,8 @@ import MVMCore.MVMCoreViewProtocol public protocol MoleculeViewProtocol: UIView, ModelHandlerProtocol { + var model: MoleculeModelProtocol? { get set } + /// Initializes the view with the model init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) @@ -33,6 +35,11 @@ public protocol MoleculeViewProtocol: UIView, ModelHandlerProtocol { extension MoleculeViewProtocol { + public var model: MoleculeModelProtocol? { + get { nil } + set { } + } + /// Calls set with model public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.init(frame: .zero) @@ -101,15 +108,3 @@ public extension ModelRegistry { } } } - -public protocol MoleculeViewModelProtocol: UIView { - - var moleculeModel: MoleculeModelProtocol? { get } -} - -public extension MoleculeViewModelProtocol { - - var moleculeModel: MoleculeModelProtocol? { - get { nil } - } -} diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index c744452e..f4c7c418 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -165,8 +165,3 @@ extension Button: AppleGuidelinesProtocol { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } } - -extension Button: MoleculeViewModelProtocol { - - public var moleculeModel: MoleculeModelProtocol? { model } -} diff --git a/MVMCoreUI/Containers/NavigationController/NavigationController.swift b/MVMCoreUI/Containers/NavigationController/NavigationController.swift index 8f72a792..5c7a9757 100644 --- a/MVMCoreUI/Containers/NavigationController/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController/NavigationController.swift @@ -83,6 +83,11 @@ import Combine } extension NavigationController: MVMCoreViewManagerProtocol { + + public func getAccessibilityElements() -> [Any]? { + nil + } + public func getCurrentViewController() -> UIViewController? { guard let topViewController = topViewController else { return nil } return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index 20f4449c..55e1071c 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -249,6 +249,11 @@ public extension MVMCoreUISplitViewController { } extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol { + + public func getAccessibilityElements() -> [Any]? { + nil + } + public func getCurrentViewController() -> UIViewController? { navigationController?.getCurrentViewController() } diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index 458c0169..24c1ed23 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -307,6 +307,10 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, } } + @objc public func getAccessibilityElements() -> [Any]? { + [tabs] + } + open func newDataReceived(in viewController: UIViewController) { manager?.newDataReceived?(in: viewController) hideNavigationBarLine(true) From 281f1e8f52572d98c59db3a45b07ceb0861e329e Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 12 Oct 2023 12:52:23 -0400 Subject: [PATCH 067/112] Fix extra space on the left of title issue for ExternalLink --- MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift index 9e5cd108..3e8fa804 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift @@ -29,7 +29,8 @@ open class ExternalLink: Link { open override func setup() { super.setup() - + contentHorizontalAlignment = .left + let image = MVMCoreUIUtility.imageNamed("externalLink") exportImageView = UIImageView(image: image?.withRenderingMode(.alwaysTemplate)) From 521eaa7c159d002874b47b631f507edc933ccb3f Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Fri, 13 Oct 2023 22:28:00 +0530 Subject: [PATCH 068/112] Added RotorHandler & addressed review comments --- MVMCoreUI.xcodeproj/project.pbxproj | 6 +- .../Accessibility/AccessibilityHandler.swift | 162 ++----------- MVMCoreUI/Accessibility/RotorHandler.swift | 228 ++++++++++++++++++ MVMCoreUI/Alerts/AlertOperation.swift | 2 +- .../Templates/MoleculeListTemplate.swift | 4 +- .../ThreeLayerTableViewController.swift | 11 +- MVMCoreUI/OtherHandlers/CoreUIObject.swift | 23 +- MVMCoreUI/OtherHandlers/MVMCoreUISession.m | 4 +- 8 files changed, 283 insertions(+), 157 deletions(-) create mode 100644 MVMCoreUI/Accessibility/RotorHandler.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4e04dafc..d1cb1f65 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -169,6 +169,7 @@ 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; + 71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; }; @@ -756,6 +757,7 @@ 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; + 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = ""; }; @@ -1457,6 +1459,7 @@ isa = PBXGroup; children = ( 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */, + 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */, ); path = Accessibility; sourceTree = ""; @@ -3012,6 +3015,7 @@ D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */, D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, + 71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */, AA71AD3E24A32FCE00ACA76F /* HeadersH2LinkModel.swift in Sources */, 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */, diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index f2bfd397..327681e8 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -49,49 +49,24 @@ public class AccessbilityOperation: MVMCoreOperation { } open class AccessibilityHandler { - - enum RotorType: String, CaseIterable { - case button = "Buttons" - case header = "Header" - - var trait: UIAccessibilityTraits { - switch self { - case .button: - return .button - case .header: - return .header - } - } - - func getUIAccessibilityCustomRotor(itemSearch: @escaping UIAccessibilityCustomRotor.Search) -> UIAccessibilityCustomRotor? { - var accessibilityCustomRotor: UIAccessibilityCustomRotor? - switch self { - case .header: - accessibilityCustomRotor = UIAccessibilityCustomRotor(systemType: .heading, itemSearch: itemSearch) - default: - accessibilityCustomRotor = UIAccessibilityCustomRotor(name: rawValue, itemSearch: itemSearch) - } - return accessibilityCustomRotor - } - } - public static func shared() -> Self? { guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } return MVMCoreActionUtility.fatalClassCheck(object: shared) } - public var accessibilityId: String? public var previousAccessiblityElement: Any? public var anyCancellable: Set = [] - public weak var delegate: MVMCoreViewControllerProtocol? - private var rotorIndexes: [RotorType: Int] = [:] - private var hasTopNotificationInPage: Bool { NotificationHandler.shared()?.isNotificationShowing() ?? false } + public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } + public weak var delegateObject: MVMCoreUIDelegateObject? + private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("userMessage") != nil } private let accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 return queue }() + + lazy var rotorHandler = RotorHandler(accessibilityHandler: self) public init() { registerForFocusChanges() @@ -129,17 +104,22 @@ open class AccessibilityHandler { } } +/** + When we push a new viewcontroller on to a Navigation stack from iOS 13+ Accessibility voiceover is going to the element inside of the viewcontroller. Not treating navigationController left/back bar button as first element. So alternatively we are setting accessibility elements in viewWillAppear untill viewDidAppear then we are resetting back So that there will not be accessibility order issue. + https://developer.apple.com/forums/thread/655359 + https://developer.apple.com/forums/thread/675427 + */ extension AccessibilityHandler { public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { - rotorIndexes = [:] previousAccessiblityElement = nil + rotorHandler.onPageNew(rootMolecules: rootMolecules, delegateObject) guard let loadObject = (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.loadObject else { return } accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId") if let announcementText = loadObject?.pageJSON?.optionalStringForKey("announcementText") { post(notification: .announcement, argument: announcementText) } - delegate = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol + self.delegateObject = delegateObject } //MARK: - PageVisibiltyBehaviour @@ -158,10 +138,12 @@ extension AccessibilityHandler { ///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered. ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { - identifyAndPrepareRotors(delegateObject) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - (delegateObject?.moleculeDelegate as? UIViewController)?.view.accessibilityElements = nil + defer { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + (delegateObject?.moleculeDelegate as? UIViewController)?.view.accessibilityElements = nil + } } + rotorHandler.onPageShown(delegateObject) guard let accessibilityElement = getPreDefinedFocusedElementIfAny() else { return } accessibilityId = nil if hasTopNotificationInPage { @@ -173,115 +155,19 @@ extension AccessibilityHandler { //MARK: - Accessibility Methods private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { - var currentController = delegateObject?.moleculeDelegate as? UIViewController + guard var currentController = delegateObject?.moleculeDelegate as? UIViewController, + currentController.navigationController != nil else { return }///If no navigationController, we can go with the default behaviour of Accessibility voiceover. var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController] if let manager = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? MVMCoreViewManagerProtocol & UIViewController), - let managerAccessibilityElements = manager.getAccessibilityElements() { + let managerAccessibilityElements = manager.getAccessibilityElements() { accessibilityElements.append(contentsOf: managerAccessibilityElements) accessibilityElements.append(contentsOf: manager.view.subviews) currentController = manager } else { - accessibilityElements.append(contentsOf: currentController?.view.subviews ?? []) + accessibilityElements.append(contentsOf: currentController.view.subviews) } accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - currentController?.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } - - //MARK: - Rotor Methods - private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) { - var rotorElements: [UIAccessibilityCustomRotor] = [] - let currentViewController = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObject?.moleculeDelegate as? ViewController) - for type in RotorType.allCases { - if let elements = getTraitMappedElements(currentViewController, type: type), - let rotor = createRotor(elements, for: type) { - rotorElements.append(rotor) - } - } - currentViewController?.navigationController?.accessibilityCustomRotors = rotorElements - } - - private func getTraitMappedElements(_ template: ViewController?, type: RotorType) -> [Any]? { - var accessibilityElements: [Any?] = [] - switch type { - case .button: - accessibilityElements.append(contentsOf: template?.navigationItem.leftBarButtonItems ?? []) - accessibilityElements.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) - case .header: - accessibilityElements.append(template?.navigationItem.titleView) - } - if let tabs = (template as? SubNavManagerController)?.tabs { - accessibilityElements.append(contentsOf: tabs.subviews.filter { - $0.accessibilityTraits.contains(type.trait) - }) - } - if let rotorElements = getRotorElementsFrom(template: template, type: type) { - accessibilityElements.append(contentsOf: rotorElements) - } - if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { - accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { - $0.accessibilityTraits.contains(type.trait) - }) - } - return accessibilityElements.compactMap { $0 } - } - - private func getRotorElementsFrom(template: ViewController?, type: RotorType) -> [Any]? { - if let currentViewController = template as? MoleculeListTemplate, - let templateModel = currentViewController.templateModel, - let tableView = currentViewController.tableView { //List templates - var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] ///Identifying the trait mapped elements models - var currentIndexPath: IndexPath? - - rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in - if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = currentViewController.getIndexPath(for: listModel) { - currentIndexPath = indexPath - } - var result = result - if (model.accessibilityTraits?.contains(type.trait) ?? false), let currentIndexPath { - result.append((model, currentIndexPath)) - } - return result - }) - - let headerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - - let footerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableFooterView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - - let otherInteractiveElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }, excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - - return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements - } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates - return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - } - return nil - } - - private func createRotor(_ elements: [Any], for type: RotorType) -> UIAccessibilityCustomRotor? { - guard !elements.isEmpty else { return nil } - return type.getUIAccessibilityCustomRotor { [weak self] predicate in - guard let self else { return UIAccessibilityCustomRotorItemResult() } - var rotorIndex = self.rotorIndexes[type] ?? 0 - if predicate.searchDirection == .next { - rotorIndex += 1 - if rotorIndex > elements.count { - rotorIndex = 1 - } - } else { - rotorIndex -= 1 - if rotorIndex <= 0 { - rotorIndex = elements.count - } - } - var rotorElement = elements[rotorIndex - 1] - if let tableView = (self.delegate as? MoleculeListTemplate)?.tableView, - let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates - tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) - rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [tableView.cellForRow(at: element.indexPath)].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any - } - self.rotorIndexes[type] = rotorIndex - post(notification: .layoutChanged, argument: rotorElement) - return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) - } + currentController.view.accessibilityElements = accessibilityElements.compactMap { $0 } } } @@ -309,7 +195,7 @@ extension AccessibilityHandler { }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationWillDismiss .sink { [weak self] (view, model) in - self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"), priority: .veryHigh) + self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed")) }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationDismissed .sink { [weak self] (view, model) in @@ -333,7 +219,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTra } //MARK: - PageMoleculeTransformationBehavior - public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + open func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { accessibilityHandler?.onPageNew(rootMolecules: rootMolecules, delegateObject) } diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift new file mode 100644 index 00000000..98841cc0 --- /dev/null +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -0,0 +1,228 @@ +// +// RotorHandler.swift +// MVMCoreUI +// +// Created by Bandaru, Krishna Kishore on 13/10/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import Combine + +fileprivate enum RotorType: String, CaseIterable { + + case button = "Buttons" + case header = "Header" + + var trait: UIAccessibilityTraits { + switch self { + case .button: + return .button + case .header: + return .header + } + } + + func getUIAccessibilityCustomRotor(itemSearch: @escaping UIAccessibilityCustomRotor.Search) -> UIAccessibilityCustomRotor? { + var accessibilityCustomRotor: UIAccessibilityCustomRotor? + switch self { + case .header: + accessibilityCustomRotor = UIAccessibilityCustomRotor(systemType: .heading, itemSearch: itemSearch) + default: + accessibilityCustomRotor = UIAccessibilityCustomRotor(name: rawValue, itemSearch: itemSearch) + } + return accessibilityCustomRotor + } +} + +public protocol RotorViewElementsProtocol: UIViewController { + var topView: UIView? { get set } + var middleView: UIView? { get set } + var bottomView: UIView? { get set } +} + +public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol { + func scrollRectToVisible(_ rect: CGRect, animated: Bool) +} + +public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol { + func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) -> Void + func cellForRow(at indexPath: IndexPath) -> UIView? +} + +struct RotorElement { + let indexPath: IndexPath + let model: MoleculeModelProtocol +} + +class RotorHandler { + + private var rotorIndexes: [RotorType: Int] = [:] + private var rotorElements: [RotorType: [Any]] = [:] + private var anyCancellable: Set = [] + private weak var delegateObject: MVMCoreUIDelegateObject? + private weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } + private weak var accessibilityHandler: AccessibilityHandler? + + init(accessibilityHandler: AccessibilityHandler?) { + self.accessibilityHandler = accessibilityHandler + registerForVoiceOverChanges() + } + + private func registerForVoiceOverChanges() { + NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) + .sink { [weak self] _ in + guard UIAccessibility.isVoiceOverRunning, (self?.rotorElements.isEmpty ?? true) else { return } + self?.identifyAndPrepareRotors(self?.delegateObject) + }.store(in: &anyCancellable) + } + + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + rotorIndexes = [:] + rotorElements = [:] + self.delegateObject = delegateObject + } + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + identifyAndPrepareRotors(delegateObject) + } + + private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) { + guard UIAccessibility.isVoiceOverRunning, + let currentViewController = (delegateObject?.moleculeDelegate as? (MVMCoreViewControllerProtocol & UIViewController)) else { return } + var customRotors: [UIAccessibilityCustomRotor] = [] + for type in RotorType.allCases { + if let elements = getTraitMappedElements(currentViewController, type: type), + !elements.isEmpty, + let rotor = createRotor(for: type) { + rotorElements[type] = elements + customRotors.append(rotor) + } + } + currentViewController.accessibilityCustomRotors = customRotors + } + + private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { + var accessibilityElements: [Any?] = [] + if let manager = (template as? MVMCoreViewManagerViewControllerProtocol)?.manager as? (MVMCoreViewControllerProtocol & UIViewController) { + accessibilityElements.append(contentsOf: getRotorElementsFrom(manager: manager, type: type) ?? []) + } + if let rotorElements = getRotorElementsFrom(template: template, type: type) { + accessibilityElements.append(contentsOf: rotorElements) + } + if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { + accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { + $0.accessibilityTraits.contains(type.trait) + }) + } + return accessibilityElements.compactMap { $0 } + } + + private func getRotorElementsFrom(manager: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { + guard let elements = (manager as? MVMCoreViewManagerProtocol)?.getAccessibilityElements() as? [UIView] else { return nil } + return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: elements).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + } + + private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { + guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else { + return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] //BAU Pages + } + let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + var reusableViewRotorElements: [Any] = [] + if let middleView = template.middleView { + if middleView.isKind(of: UITableView.self), + let template = template as? (any MoleculeListProtocol & ViewController) { + reusableViewRotorElements = getRotorElements(from: template, type: type) ?? [] + } else { + reusableViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [middleView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + } + } + let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + return topViewRotorElements + reusableViewRotorElements + remainingRotorElements + bottomViewRotorElements + } + + private func getRotorElements(from template: (any MoleculeListProtocol & ViewController)?, type: RotorType) -> [Any]? { + guard let templateModel = template?.model, + let moleculeList = template else { return nil } + var rotorElements: [RotorElement] = [] + var traitIndexPath: IndexPath? + rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements) { result, model, depth in + if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = moleculeList.getIndexPath(for: listModel) { + traitIndexPath = indexPath + } + var result = result + if (model.accessibilityTraits?.contains(type.trait) ?? false), let traitIndexPath { + result.append(.init(indexPath: traitIndexPath, model: model)) + } + return result + } + return rotorElements + } + + private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? { + return type.getUIAccessibilityCustomRotor { [weak self] predicate in + guard let self, let elements = self.rotorElements[type] else { return UIAccessibilityCustomRotorItemResult() } + var rotorIndex = self.rotorIndexes[type] ?? 0 + if predicate.searchDirection == .next { + rotorIndex += 1 + if rotorIndex > elements.count { + rotorIndex = 1 + } + } else { + rotorIndex -= 1 + if rotorIndex <= 0 { + rotorIndex = elements.count + } + } + var rotorElement = elements[rotorIndex - 1] + if let element = rotorElement as? RotorElement, + let controller = self.delegate as? RotorListTypeDelegateProtocol { + controller.scrollToRow(at: element.indexPath, at: .middle, animated: false) + guard let cellView = controller.cellForRow(at: element.indexPath) else { return UIAccessibilityCustomRotorItemResult() } + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any + } else { + if let viewElement = (rotorElement as? UIView) { + let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view) + (self.delegate as? RotorScrollDelegateProtocol)?.scrollRectToVisible(convertedFrame, animated: false) + } + } + self.rotorIndexes[type] = rotorIndex + self.accessibilityHandler?.post(notification: .layoutChanged, argument: rotorElement) + return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) + } + } +} + +public extension RotorViewElementsProtocol { + var topView: UIView? { nil } + var middleView: UIView? { nil } + var bottomView: UIView? { nil } +} + +extension RotorScrollDelegateProtocol { + public func scrollRectToVisible(_ rect: CGRect, animated: Bool) { } +} + +extension RotorListTypeDelegateProtocol { + func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { } + func cellForRow(at indexPath: IndexPath) -> UIView? { nil } +} + +extension ThreeLayerTableViewController: RotorListTypeDelegateProtocol { + + public func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { + tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: animated) + } + + public func cellForRow(at indexPath: IndexPath) -> UIView? { + tableView.cellForRow(at: indexPath) + } +} + +extension ThreeLayerViewController: RotorScrollDelegateProtocol { + + public func scrollRectToVisible(_ rect: CGRect, animated: Bool) { + scrollView?.scrollRectToVisible(rect, animated: animated) + } +} diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index 088a6ff5..50b71e46 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -73,7 +73,7 @@ public class AlertOperation: MVMCoreOperation { if await !self.properties.getIsDisplayed() { self.markAsFinished() } else { - (CoreUIObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject) + (MVMCoreObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject) if self.isCancelled { await self.dismissAlertView() } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 950b6785..a113ef5f 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -294,8 +294,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol let classMoleculeB = moleculeB as? NSObjectProtocol { return classMoleculeA === classMoleculeB } - // Do json check - return moleculeA.toJSON() == moleculeB.toJSON() + // ID check + return moleculeA.id == moleculeB.id } } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 4faff136..a6d6c7b5 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -8,15 +8,20 @@ import UIKit -open class ThreeLayerTableViewController: ProgrammaticTableViewController { +open class ThreeLayerTableViewController: ProgrammaticTableViewController, RotorViewElementsProtocol { + //-------------------------------------------------- // MARK: - Main Views //-------------------------------------------------- - private var topView: UIView? - private var bottomView: UIView? private var headerView: UIView? private var footerView: UIView? + public var topView: UIView? + public var middleView: UIView? { + get { tableView } + set { } + } + public var bottomView: UIView? //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index dd82a34f..131c2287 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -9,7 +9,12 @@ import UIKit import MVMCore -@objcMembers open class CoreUIObject: MVMCoreObject { +@objcMembers +public class CoreUIObject: NSObject { + private static var singleton = CoreUIObject() + public static func sharedInstance() -> CoreUIObject? { singleton } + private override init() {} + public var alertHandler: AlertHandler? public var topNotificationHandler: NotificationHandler? { didSet { @@ -17,16 +22,14 @@ import MVMCore } } public var accessibilityHandler: AccessibilityHandler? - - open override func defaultInitialSetup() { + + public func defaultInitialSetup() { + MVMCoreObject.sharedInstance()?.defaultInitialSetup() CoreUIModelMapping.registerObjects() - loadHandler = MVMCoreLoadHandler() - cache = MVMCoreCache() - session = MVMCoreUISession() - sessionHandler = MVMCoreSessionTimeHandler() - actionHandler = MVMCoreUIActionHandler() - viewControllerMapping = MVMCoreUIViewControllerMappingObject() - loggingDelegate = MVMCoreUILoggingHandler() + MVMCoreObject.sharedInstance()?.session = MVMCoreUISession() + MVMCoreObject.sharedInstance()?.actionHandler = MVMCoreUIActionHandler() + MVMCoreObject.sharedInstance()?.viewControllerMapping = MVMCoreUIViewControllerMappingObject() + MVMCoreObject.sharedInstance()?.loggingDelegate = MVMCoreUILoggingHandler() alertHandler = AlertHandler() accessibilityHandler = AccessibilityHandler() } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m index e1ca231f..3b1d5913 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m @@ -9,8 +9,8 @@ #import "MVMCoreUISession.h" #import "MFLoadingViewController.h" #import "NSLayoutConstraint+MFConvenience.h" -#import -@import MVMCore.MVMCoreObject; +@import MVMCore.MVMCoreLoadingOverlayDelegateProtocol; +@import MVMCore.Swift; @interface MVMCoreUISession () From 67dfe37e97dd9c0cfe25a2e70a0c3f84b0ec3d33 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Mon, 16 Oct 2023 12:49:35 +0530 Subject: [PATCH 069/112] Addressed review comments and removed model based traits for button --- .../Accessibility/AccessibilityHandler.swift | 2 +- MVMCoreUI/Accessibility/RotorHandler.swift | 17 ++++++++--------- .../Atomic/Atoms/Buttons/ButtonModel.swift | 3 --- MVMCoreUI/BaseClasses/Button.swift | 4 ---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 327681e8..cefae3ea 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -59,7 +59,7 @@ open class AccessibilityHandler { public var anyCancellable: Set = [] public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } public weak var delegateObject: MVMCoreUIDelegateObject? - private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("userMessage") != nil } + private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("messageStyle") != nil } private let accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index 98841cc0..2d4b6e9c 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -125,7 +125,7 @@ class RotorHandler { private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else { - return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] //BAU Pages + return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] } let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] var reusableViewRotorElements: [Any] = [] @@ -163,19 +163,18 @@ class RotorHandler { private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? { return type.getUIAccessibilityCustomRotor { [weak self] predicate in guard let self, let elements = self.rotorElements[type] else { return UIAccessibilityCustomRotorItemResult() } - var rotorIndex = self.rotorIndexes[type] ?? 0 + var rotorIndex = self.rotorIndexes[type] ?? -1 if predicate.searchDirection == .next { - rotorIndex += 1 - if rotorIndex > elements.count { - rotorIndex = 1 + if rotorIndex + 1 < elements.count { + rotorIndex += 1 } } else { - rotorIndex -= 1 - if rotorIndex <= 0 { - rotorIndex = elements.count + if rotorIndex > 0 { + rotorIndex -= 1 } } - var rotorElement = elements[rotorIndex - 1] + guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() } + var rotorElement = elements[rotorIndex] if let element = rotorElement as? RotorElement, let controller = self.delegate as? RotorListTypeDelegateProtocol { controller.scrollToRow(at: element.indexPath, at: .middle, animated: false) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 92b50d29..08afefde 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -35,7 +35,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var size: Styler.Button.Size? = .standard public var groupName: String = "" public var inverted: Bool = false - public var accessibilityTraits: UIAccessibilityTraits? public lazy var enabledColors: FacadeElements = (fill: enabled_fillColor(), text: enabled_textColor(), @@ -196,7 +195,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case disabledTextColor case disabledBorderColor case width - case accessibilityTraits } //-------------------------------------------------- @@ -209,7 +207,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) - accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits) title = try typeContainer.decode(String.self, forKey: .title) action = try typeContainer.decodeModel(codingKey: .action) diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index f4c7c418..15ea1e03 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -107,10 +107,6 @@ public typealias ButtonAction = (Button) -> () isEnabled = model.enabled } - if let accessibilityTraits = model.accessibilityTraits { - self.accessibilityTraits = accessibilityTraits - } - guard let model = model as? ButtonModelProtocol else { return } set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) From cb50234090656b9b6aa7380288bcef61cee6831e Mon Sep 17 00:00:00 2001 From: Keerthy Date: Mon, 16 Oct 2023 13:00:12 +0530 Subject: [PATCH 070/112] Spelling refactoring --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index cefae3ea..488ae403 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -11,7 +11,7 @@ import Combine import MVMCore import WebKit -public class AccessbilityOperation: MVMCoreOperation { +public class AccessibilityOperation: MVMCoreOperation { private let argument: Any? private let notificationType: UIAccessibility.Notification @@ -84,8 +84,8 @@ open class AccessibilityHandler { public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } - let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument) - accessibilityOperationQueue.addOperation(accessbilityOperation) + let AccessibilityOperation = AccessibilityOperation(notificationType: type, argument: argument) + accessibilityOperationQueue.addOperation(AccessibilityOperation) } //To get first focus element on the screen From f73dbee1902a3c91c83aa6134382ef713b019a73 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Mon, 16 Oct 2023 18:48:49 +0530 Subject: [PATCH 071/112] Added rotor for checkbox --- MVMCoreUI/Accessibility/RotorHandler.swift | 45 +++++++++++++------ .../Atoms/Views/CheckboxLabelModel.swift | 3 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index 2d4b6e9c..5819b198 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -13,6 +13,7 @@ fileprivate enum RotorType: String, CaseIterable { case button = "Buttons" case header = "Header" + case checkbox = "Checkbox" var trait: UIAccessibilityTraits { switch self { @@ -20,6 +21,26 @@ fileprivate enum RotorType: String, CaseIterable { return .button case .header: return .header + default: + return .none + } + } + + var modelFilter: ((MoleculeModelProtocol) -> Bool) { + switch self { + case .checkbox: + return { $0 is CheckboxModel } + default: + return { $0.accessibilityTraits?.contains(trait) ?? false } + } + } + + var filter: ((UIView) -> Bool) { + switch self { + case .checkbox: + return { $0 is Checkbox } + default: + return { $0.accessibilityTraits.contains(trait) } } } @@ -35,7 +56,7 @@ fileprivate enum RotorType: String, CaseIterable { } } -public protocol RotorViewElementsProtocol: UIViewController { +public protocol RotorViewElementsProtocol: MVMCoreViewControllerProtocol { var topView: UIView? { get set } var middleView: UIView? { get set } var bottomView: UIView? { get set } @@ -89,7 +110,7 @@ class RotorHandler { private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) { guard UIAccessibility.isVoiceOverRunning, - let currentViewController = (delegateObject?.moleculeDelegate as? (MVMCoreViewControllerProtocol & UIViewController)) else { return } + let currentViewController = (delegateObject?.moleculeDelegate as? (MVMCoreViewControllerProtocol & UIViewController)) else { return } var customRotors: [UIAccessibilityCustomRotor] = [] for type in RotorType.allCases { if let elements = getTraitMappedElements(currentViewController, type: type), @@ -111,34 +132,32 @@ class RotorHandler { accessibilityElements.append(contentsOf: rotorElements) } if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { - accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { - $0.accessibilityTraits.contains(type.trait) - }) + accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { type.filter($0) }) } return accessibilityElements.compactMap { $0 } } private func getRotorElementsFrom(manager: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { guard let elements = (manager as? MVMCoreViewManagerProtocol)?.getAccessibilityElements() as? [UIView] else { return nil } - return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: elements).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: elements).filter { type.filter($0) } as [Any] } private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else { - return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { type.filter($0) } as [Any] } - let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { type.filter($0) } as [Any] var reusableViewRotorElements: [Any] = [] if let middleView = template.middleView { if middleView.isKind(of: UITableView.self), let template = template as? (any MoleculeListProtocol & ViewController) { reusableViewRotorElements = getRotorElements(from: template, type: type) ?? [] } else { - reusableViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [middleView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + reusableViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [middleView].compactMap { $0 }).filter { type.filter($0) } as [Any] } } - let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] - let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] + let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] + let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] return topViewRotorElements + reusableViewRotorElements + remainingRotorElements + bottomViewRotorElements } @@ -152,7 +171,7 @@ class RotorHandler { traitIndexPath = indexPath } var result = result - if (model.accessibilityTraits?.contains(type.trait) ?? false), let traitIndexPath { + if type.modelFilter(model), let traitIndexPath { result.append(.init(indexPath: traitIndexPath, model: model)) } return result @@ -179,7 +198,7 @@ class RotorHandler { let controller = self.delegate as? RotorListTypeDelegateProtocol { controller.scrollToRow(at: element.indexPath, at: .middle, animated: false) guard let cellView = controller.cellForRow(at: element.indexPath) else { return UIAccessibilityCustomRotorItemResult() } - rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any } else { if let viewElement = (rotorElement as? UIView) { let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view) diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift index ca0acd79..9618cfab 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift @@ -15,7 +15,7 @@ public enum CheckboxPosition: String, Codable { case bottom } -@objcMembers open class CheckboxLabelModel: MoleculeModelProtocol { +@objcMembers open class CheckboxLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { open class var identifier: String { "checkboxLabel" } public var moleculeName: String = CheckboxLabelModel.identifier @DecodableDefault.UUIDString public var id: String @@ -25,6 +25,7 @@ public enum CheckboxPosition: String, Codable { public var checkbox: CheckboxModel public var label: LabelModel + public var children: [MoleculeModelProtocol] { [checkbox, label] } //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- From 20a4a2c87be4cf897d2f95d62f2adc9c811d4aa1 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 17 Oct 2023 14:57:25 +0530 Subject: [PATCH 072/112] Moving progress indicator below the text in the loading overlay screen --- MVMCoreUI/BaseControllers/MFLoadingViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/BaseControllers/MFLoadingViewController.m b/MVMCoreUI/BaseControllers/MFLoadingViewController.m index 567790aa..e573fcd8 100644 --- a/MVMCoreUI/BaseControllers/MFLoadingViewController.m +++ b/MVMCoreUI/BaseControllers/MFLoadingViewController.m @@ -47,8 +47,8 @@ infoLabel.translatesAutoresizingMaskIntoConstraints = NO; self.indicatorText = infoLabel; - [loadingStack addArrangedSubview:activityIndicatorView]; [loadingStack addArrangedSubview:infoLabel]; + [loadingStack addArrangedSubview:activityIndicatorView]; loadingStack.translatesAutoresizingMaskIntoConstraints = NO; [view addSubview:loadingStack]; From 0a42ebb032ccad02ab34f1211e3225a6a0f756f3 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 17 Oct 2023 20:14:19 +0530 Subject: [PATCH 073/112] Setting empty string to the text in the loading overlay --- MVMCoreUI/BaseControllers/MFLoadingViewController.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/BaseControllers/MFLoadingViewController.m b/MVMCoreUI/BaseControllers/MFLoadingViewController.m index e573fcd8..a2edee05 100644 --- a/MVMCoreUI/BaseControllers/MFLoadingViewController.m +++ b/MVMCoreUI/BaseControllers/MFLoadingViewController.m @@ -81,9 +81,12 @@ } - (void)startLoadingWith:(nullable NSString *) text{ - if([text length] != 0){ + if(text != nil){ self.indicatorText.text = text; self.indicatorText.accessibilityLabel = text; + } else { + self.indicatorText.text = @""; + self.indicatorText.accessibilityLabel = @""; } [self.activityIndicator resumeSpinner]; } From 436d4c746bd6af2a4dbc96b6ba94163272882514 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 19 Oct 2023 10:35:27 +0530 Subject: [PATCH 074/112] review comments and enhancements in rotorhandler --- .../Accessibility/AccessibilityHandler.swift | 11 +- MVMCoreUI/Accessibility/RotorHandler.swift | 134 +++++++++++++----- .../Protocols/MoleculeListProtocol.swift | 6 + .../Atomic/Templates/CollectionTemplate.swift | 8 ++ .../Templates/MoleculeListTemplate.swift | 9 +- .../ThreeLayerCollectionViewController.swift | 10 +- .../ThreeLayerViewController.swift | 8 +- 7 files changed, 127 insertions(+), 59 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 488ae403..32d6a0f2 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -22,13 +22,13 @@ public class AccessibilityOperation: MVMCoreOperation { } public override func main() { - guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else { - stop() + guard UIAccessibility.isVoiceOverRunning else { + markAsFinished() return } Task { @MainActor [weak self] in guard let self = self, !self.isCancelled else { - self?.stop() + self?.markAsFinished() return } UIAccessibility.post(notification: self.notificationType, argument: self.argument) @@ -41,11 +41,6 @@ public class AccessibilityOperation: MVMCoreOperation { } } } - - public func stop() { - guard isCancelled else { return } - markAsFinished() - } } open class AccessibilityHandler { diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index 5819b198..3c3fdbce 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -67,13 +67,22 @@ public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol { } public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol { - func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) -> Void + func scrollToRow(at indexPath: IndexPath, animated: Bool) -> Void func cellForRow(at indexPath: IndexPath) -> UIView? + func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? } struct RotorElement { - let indexPath: IndexPath + + let indexPath: IndexPath? let model: MoleculeModelProtocol + let carouselItemModel: MoleculeModelProtocol? + + init(indexPath: IndexPath? = nil, model: MoleculeModelProtocol, carouselItemModel: MoleculeModelProtocol? = nil) { + self.indexPath = indexPath + self.model = model + self.carouselItemModel = carouselItemModel + } } class RotorHandler { @@ -94,7 +103,9 @@ class RotorHandler { NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) .sink { [weak self] _ in guard UIAccessibility.isVoiceOverRunning, (self?.rotorElements.isEmpty ?? true) else { return } - self?.identifyAndPrepareRotors(self?.delegateObject) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self?.identifyAndPrepareRotors(self?.delegateObject) + } }.store(in: &anyCancellable) } @@ -120,7 +131,7 @@ class RotorHandler { customRotors.append(rotor) } } - currentViewController.accessibilityCustomRotors = customRotors + currentViewController.view.accessibilityCustomRotors = customRotors } private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { @@ -143,40 +154,50 @@ class RotorHandler { } private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { - guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else { - return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { type.filter($0) } as [Any] + guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & ViewController) else { + return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { type.filter($0) } as [Any] //BAU pages } - let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { type.filter($0) } as [Any] - var reusableViewRotorElements: [Any] = [] - if let middleView = template.middleView { - if middleView.isKind(of: UITableView.self), - let template = template as? (any MoleculeListProtocol & ViewController) { - reusableViewRotorElements = getRotorElements(from: template, type: type) ?? [] - } else { - reusableViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [middleView].compactMap { $0 }).filter { type.filter($0) } as [Any] - } - } - let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] - let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] - return topViewRotorElements + reusableViewRotorElements + remainingRotorElements + bottomViewRotorElements + let rotorElements = getRotorElements(from: template, type: type) + let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] //Other elements added to view if any. + return rotorElements + remainingRotorElements } - private func getRotorElements(from template: (any MoleculeListProtocol & ViewController)?, type: RotorType) -> [Any]? { - guard let templateModel = template?.model, - let moleculeList = template else { return nil } - var rotorElements: [RotorElement] = [] + private func getRotorElements(from template: (MVMCoreViewControllerProtocol & ViewController & RotorViewElementsProtocol)?, type: RotorType) -> [Any] { + guard let templateModel = template?.model as? ThreeLayerModelBase else { return [] } + var rotorElements: [Any] = [] + if let headerView = template?.topView { + rotorElements.append(contentsOf: MVMCoreUIUtility.findViews(by: UIView.self, views: [headerView].compactMap { $0 }).filter { type.filter($0) }) + } + let molecules = templateModel.rootMolecules.filter { !($0.id == templateModel.header?.id || $0.id == templateModel.footer?.id) } + rotorElements.append(contentsOf: getRotorElements(from: molecules, template: template, type: type)) + if let bottomView = template?.bottomView { + rotorElements.append(contentsOf: MVMCoreUIUtility.findViews(by: UIView.self, views: [bottomView].compactMap { $0 }).filter { type.filter($0) }) + } + return rotorElements + } + + private func getRotorElements(from molecules: [MoleculeModelProtocol], template: (MVMCoreViewControllerProtocol & ViewController)?, type: RotorType) -> [Any] { var traitIndexPath: IndexPath? - rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements) { result, model, depth in - if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = moleculeList.getIndexPath(for: listModel) { + var carouselItemModel: MoleculeModelProtocol? + var carouselDepth: Int? + let rotorElements: [RotorElement] = molecules.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: []) { result, model, depth in + if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), + let indexPath = (template as? RotorListTypeDelegateProtocol)?.getIndexPathFor(molecule: listModel) { traitIndexPath = indexPath } + if let _carouselDepth = carouselDepth, depth < _carouselDepth { + carouselDepth = nil + carouselItemModel = nil + } + if model is CarouselModel { carouselDepth = depth } + carouselItemModel = model is CarouselItemModelProtocol ? model : carouselItemModel var result = result - if type.modelFilter(model), let traitIndexPath { - result.append(.init(indexPath: traitIndexPath, model: model)) + if type.modelFilter(model) { + result.append(.init(indexPath: traitIndexPath, model: model, carouselItemModel: carouselItemModel)) } return result } - return rotorElements + return rotorElements as [Any] } private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? { @@ -195,10 +216,28 @@ class RotorHandler { guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() } var rotorElement = elements[rotorIndex] if let element = rotorElement as? RotorElement, - let controller = self.delegate as? RotorListTypeDelegateProtocol { - controller.scrollToRow(at: element.indexPath, at: .middle, animated: false) - guard let cellView = controller.cellForRow(at: element.indexPath) else { return UIAccessibilityCustomRotorItemResult() } - rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any + let controller = self.delegate as? (RotorViewElementsProtocol & ViewController) { + var cellView: UIView? + if let indexPath = element.indexPath, let controller = self.delegate as? (any RotorListTypeDelegateProtocol) { + controller.scrollToRow(at: indexPath, animated: false) + cellView = controller.cellForRow(at: indexPath) + } else { + cellView = controller.view + } + if let cauroselItemModel = element.carouselItemModel { + guard let carousel = MVMCoreUIUtility.findViews(by: Carousel.self, views: [cellView].compactMap { $0 }).first, + let index = (carousel.molecules?.firstIndex { $0.id == cauroselItemModel.id }) else { return UIAccessibilityCustomRotorItemResult() } + let collectionIndexPath = IndexPath(item: index, section: 0) + carousel.collectionView.scrollToItem(at: collectionIndexPath, at: .left, animated: false) + let collectionViewCell = carousel.collectionView.cellForItem(at: collectionIndexPath) + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [collectionViewCell].compactMap { $0 }).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any + } else { + rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView].compactMap { $0 }).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any + if let viewElement = (rotorElement as? UIView) { + let convertedFrame = viewElement.convert(viewElement.frame, to: controller.view) + (self.delegate as? RotorScrollDelegateProtocol)?.scrollRectToVisible(convertedFrame, animated: false) + } + } } else { if let viewElement = (rotorElement as? UIView) { let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view) @@ -223,19 +262,26 @@ extension RotorScrollDelegateProtocol { } extension RotorListTypeDelegateProtocol { - func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { } - func cellForRow(at indexPath: IndexPath) -> UIView? { nil } + + public func scrollToRow(at indexPath: IndexPath, animated: Bool) { } + public func cellForRow(at indexPath: IndexPath) -> UIView? { nil } + public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? { nil } } extension ThreeLayerTableViewController: RotorListTypeDelegateProtocol { - public func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { - tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: animated) + public func scrollToRow(at indexPath: IndexPath, animated: Bool) { + tableView.scrollToRow(at: indexPath, at: .middle, animated: animated) } public func cellForRow(at indexPath: IndexPath) -> UIView? { tableView.cellForRow(at: indexPath) } + + public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? { + guard let molecule = molecule as? (ListItemModelProtocol & MoleculeModelProtocol) else { return nil } + return (self as? MoleculeListTemplate)?.getIndexPath(for: molecule) + } } extension ThreeLayerViewController: RotorScrollDelegateProtocol { @@ -244,3 +290,19 @@ extension ThreeLayerViewController: RotorScrollDelegateProtocol { scrollView?.scrollRectToVisible(rect, animated: animated) } } + +extension ThreeLayerCollectionViewController: RotorListTypeDelegateProtocol { + + public func scrollToRow(at indexPath: IndexPath, animated: Bool) { + collectionView?.scrollToItem(at: indexPath, at: .centeredVertically, animated: animated) + } + + public func cellForRow(at indexPath: IndexPath) -> UIView? { + collectionView?.cellForItem(at: indexPath) + } + + public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? { + guard let molecule = molecule as? (CollectionItemModelProtocol & MoleculeModelProtocol) else { return nil } + return (self as? MoleculeCollectionListProtocol)?.getIndexPath(for: molecule) + } +} diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift index dfeb7b11..8f4bfee0 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift @@ -37,3 +37,9 @@ public extension MVMCoreUIDelegateObject { return (moleculeDelegate as? MoleculeListProtocol & NSObjectProtocol) } } + +public protocol MoleculeCollectionListProtocol { + + /// Asks the delegate for the index of molecule. + func getIndexPath(for molecule: CollectionItemModelProtocol & MoleculeModelProtocol) -> IndexPath? +} diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index b84792a6..3683e1e9 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -198,3 +198,11 @@ return modules } } + +extension CollectionTemplate: MoleculeCollectionListProtocol { + + public func getIndexPath(for molecule: CollectionItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { + guard let index = (moleculesInfo?.firstIndex { $0.molecule.id == molecule.id }) else { return nil } + return IndexPath(item: index, section: 0) + } +} diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index a113ef5f..30a9b5c5 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -288,14 +288,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol /// Checks if the two molecules are equal private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool { - // TODO: move this to a better approach, maybe a UUID for each model. - // Do instance check - if let classMoleculeA = moleculeA as? NSObjectProtocol, - let classMoleculeB = moleculeB as? NSObjectProtocol { - return classMoleculeA === classMoleculeB - } - // ID check - return moleculeA.id == moleculeB.id + moleculeA.id == moleculeB.id } } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 4011f4f8..05a575ae 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -9,10 +9,14 @@ import Foundation /// A view controller that has three main layers, a header, collection rows, and a footer. The header is added as a supplement header to the first section, and the footer is added as a supplement footer to the last section. This view controller allows for flexible space between the three layers to fit the screeen. -@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout { +@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout, RotorViewElementsProtocol { - private var topView: UIView? - private var bottomView: UIView? + public var topView: UIView? + public var middleView: UIView? { + set {} + get { collectionView } + } + public var bottomView: UIView? private var headerView: ContainerCollectionReusableView? private var footerView: ContainerCollectionReusableView? private let headerID = "header" diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index ec8aaaa3..4560aa09 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -9,12 +9,12 @@ import UIKit -open class ThreeLayerViewController: ProgrammaticScrollViewController { +open class ThreeLayerViewController: ProgrammaticScrollViewController, RotorViewElementsProtocol { // The three main views - var topView: UIView? - var middleView: UIView? - var bottomView: UIView? + public var topView: UIView? + public var middleView: UIView? + public var bottomView: UIView? var useMargins: Bool = false // The top view can be put outside of the scrolling area. From fdaa932c3b89812e96f28491e4268895d42f9403 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 19 Oct 2023 16:58:07 +0530 Subject: [PATCH 075/112] Enabling the loading overlay info text to take attributed string. --- MVMCoreUI/BaseControllers/MFLoadingViewController.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/BaseControllers/MFLoadingViewController.m b/MVMCoreUI/BaseControllers/MFLoadingViewController.m index a2edee05..fb7dd8f8 100644 --- a/MVMCoreUI/BaseControllers/MFLoadingViewController.m +++ b/MVMCoreUI/BaseControllers/MFLoadingViewController.m @@ -42,7 +42,7 @@ self.activityIndicator.accessibilityIdentifier = @"Loader"; [activityIndicatorView pinWidthAndHeight]; - Label *infoLabel = [Label commonLabelH3:YES]; + Label *infoLabel = [Label label]; infoLabel.textAlignment = NSTextAlignmentCenter; infoLabel.translatesAutoresizingMaskIntoConstraints = NO; self.indicatorText = infoLabel; @@ -80,12 +80,12 @@ [self.activityIndicator resumeSpinner]; } -- (void)startLoadingWith:(nullable NSString *) text{ +- (void)startLoadingWith:(nullable NSAttributedString *) text{ if(text != nil){ - self.indicatorText.text = text; - self.indicatorText.accessibilityLabel = text; + self.indicatorText.attributedText = text; + self.indicatorText.accessibilityLabel = text.string; } else { - self.indicatorText.text = @""; + self.indicatorText.attributedText = nil; self.indicatorText.accessibilityLabel = @""; } [self.activityIndicator resumeSpinner]; From b66b49d173c84b5c2970976bd9e5a7ed30a2ead5 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 19 Oct 2023 18:00:20 +0530 Subject: [PATCH 076/112] Added comments for methods --- .../Accessibility/AccessibilityHandler.swift | 102 +++++++++++++----- MVMCoreUI/Accessibility/RotorHandler.swift | 64 ++++++++++- .../Atomic/Organisms/Carousel/Carousel.swift | 2 +- 3 files changed, 138 insertions(+), 30 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 32d6a0f2..29b727b6 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -11,6 +11,7 @@ import Combine import MVMCore import WebKit +//MARK: - AccessibilityOperation public class AccessibilityOperation: MVMCoreOperation { private let argument: Any? @@ -21,6 +22,10 @@ public class AccessibilityOperation: MVMCoreOperation { self.argument = argument } + /** + This method will post accessibility notification. + If we have announcement notification then operation will wait untill announcement is finished. + */ public override func main() { guard UIAccessibility.isVoiceOverRunning else { markAsFinished() @@ -32,7 +37,7 @@ public class AccessibilityOperation: MVMCoreOperation { return } UIAccessibility.post(notification: self.notificationType, argument: self.argument) - if self.notificationType == .announcement { + if self.notificationType == .announcement {///Marking task as finished only if announcement did finished so that user will listen complete announcement. NotificationCenter.default.addObserver(forName: UIAccessibility.announcementDidFinishNotification, object: nil, queue: .main) { _ in self.markAsFinished() } @@ -43,14 +48,19 @@ public class AccessibilityOperation: MVMCoreOperation { } } +//MARK: - AccessibilityHandler +/** + AccessibilityHandler will observe the page visibility of every view controller and post notification to the first interactive element on the screen. + If we have to shift/foucs custom element on the screen on controller shown then we need to pass accessibilityId in the page response. + */ open class AccessibilityHandler { public static func shared() -> Self? { guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } return MVMCoreActionUtility.fatalClassCheck(object: shared) } - public var accessibilityId: String? - public var previousAccessiblityElement: Any? + public var accessibilityId: String? ///This property is used to post accessibility to the UIElement mapped to this accessibilityId + public var previousAccessiblityElement: Any? ///This property is capture accessiblity element public var anyCancellable: Set = [] public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } public weak var delegateObject: MVMCoreUIDelegateObject? @@ -63,34 +73,52 @@ open class AccessibilityHandler { lazy var rotorHandler = RotorHandler(accessibilityHandler: self) + /** + init method will register for focus changes + */ public init() { registerForFocusChanges() } - // MARK: - Accessibility Handler operation events + /** + This method will capture current foucsed element + */ open func capturePreviousFocusElement() { previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) } - open func postAccessbilityToPrevElement() { + /** + This method will post accessibility notification to previous captured element + */ + open func postAccessibilityToPrevElement() { post(notification: .layoutChanged, argument: previousAccessiblityElement) previousAccessiblityElement = nil } - + + /** + This method will check if voice over is running then will post notification to the mentioned argument + */ public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) { guard UIAccessibility.isVoiceOverRunning else { return } let AccessibilityOperation = AccessibilityOperation(notificationType: type, argument: argument) accessibilityOperationQueue.addOperation(AccessibilityOperation) } - //To get first focus element on the screen + /** + This method return first focused element from the screen. + */ open func getFirstFocusedElementOnScreen() -> Any? { (delegate as? UIViewController)?.navigationController?.navigationBar } - //Subclass can decide to trigger Accessibility notification on screen change. - open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true } + /** + This method is used to decide if AccessibilityHandler can post screen change notification or specific classes will take care of posting Accessibility notification + */ + open func canPostAccessibilityNotification(for viewController: UIViewController) -> Bool { true } + /** + This method is used to identify the UIElement that is mapped to accessibilityId from server response. + */ func getPreDefinedFocusedElementIfAny() -> UIView? { guard let accessibilityId, let view = (delegate as? UIViewController)?.view else { return nil } return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [view]).first { @@ -99,13 +127,12 @@ open class AccessibilityHandler { } } -/** - When we push a new viewcontroller on to a Navigation stack from iOS 13+ Accessibility voiceover is going to the element inside of the viewcontroller. Not treating navigationController left/back bar button as first element. So alternatively we are setting accessibility elements in viewWillAppear untill viewDidAppear then we are resetting back So that there will not be accessibility order issue. - https://developer.apple.com/forums/thread/655359 - https://developer.apple.com/forums/thread/675427 - */ extension AccessibilityHandler { + /** + This method is used to notify rotor handler about page loaded and capturing the accessibilityId if any. + Will announce text if the page is has announcementText in the response. + */ public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { previousAccessiblityElement = nil rotorHandler.onPageNew(rootMolecules: rootMolecules, delegateObject) @@ -117,11 +144,16 @@ extension AccessibilityHandler { self.delegateObject = delegateObject } - //MARK: - PageVisibiltyBehaviour + /** + This method is used to capture accessibility views on the screen. + If the page has accessibilityId, then it will not post any accessibility notification because respective UI mapped element can be identified only on page shown. + If it has top notification then we are capturing the first focused element and will not post any accessibility notification. + If page doesn't have any top notification or accessibilityId then it will post notification to shift foucs to first focused element on the screen. + */ public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) guard let controller = delegateObject?.moleculeDelegate as? UIViewController, - canPostAccessbilityNotification(for: controller), + canPostAccessibilityNotification(for: controller), accessibilityId == nil else { return } if hasTopNotificationInPage { previousAccessiblityElement = getFirstFocusedElementOnScreen() @@ -130,8 +162,15 @@ extension AccessibilityHandler { } } - ///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered. - ///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain + /** + This method is used to notify rotor handler about page visibility + Temp fix: - We are resetting the view accessibilityElements when focus shifted to first focused element on the screen not to have voice over struck in between view elements. + https://developer.apple.com/forums/thread/655359 + https://developer.apple.com/forums/thread/675427 + If the page has accessibilityId, i.e if server decides to manually shift focus to one of the UIElement then we are identifying the id mapped UIElement & shifting focus to that element. + If we have top notification as well in the page then we take that as priority and holding the server driven UIElement in previousAccessiblityElement and post accessibility notification. + https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain + */ public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { defer { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { @@ -148,7 +187,9 @@ extension AccessibilityHandler { } } - //MARK: - Accessibility Methods + /** + This method is used to set view elements as accessibilityElements due to the accessibility behaviour change when new controller is pushed on navigation stack from iOS13+ + */ private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { guard var currentController = delegateObject?.moleculeDelegate as? UIViewController, currentController.navigationController != nil else { return }///If no navigationController, we can go with the default behaviour of Accessibility voiceover. @@ -166,9 +207,13 @@ extension AccessibilityHandler { } } +// MARK: - AccessibilityHandler listeners @objc extension AccessibilityHandler { - // MARK: - Register with Accessibility Handler listeners + /** + This method listens for foucs changes. + When foucs is changed manually then we are cancelling existing operations. + */ private func registerForFocusChanges() { //Since focus shifted to other elements cancelling existing focus shift notifications if any NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification) @@ -177,6 +222,13 @@ extension AccessibilityHandler { }.store(in: &anyCancellable) } + /** + This method listens for top notification changes. + When top notification is about to display it will capture previous focused element. + When top notification is displayed it will post notification to that notification view + When top notification is about to dismiss then it will post announcement that top alert is closed. + When top notification is dimissed then it will post notification back to previously captured element. + */ func registerForTopNotificationsChanges() { NotificationHandler.shared()?.onNotificationWillShow .sink { [weak self] (_, model) in @@ -194,13 +246,15 @@ extension AccessibilityHandler { }.store(in: &anyCancellable) NotificationHandler.shared()?.onNotificationDismissed .sink { [weak self] (view, model) in - self?.postAccessbilityToPrevElement() + self?.postAccessibilityToPrevElement() }.store(in: &anyCancellable) } } -// MARK: - Accessibility Handler Behaviour -///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element. +// MARK: - AccessibilityHandlerBehaviour +/** + To notify AccessibilityHandler about the page visibility changes + */ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTransformationBehavior { public let accessibilityHandler: AccessibilityHandler? @@ -213,12 +267,10 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTra accessibilityHandler = AccessibilityHandler.shared() //Protocol Mandatory init method. } - //MARK: - PageMoleculeTransformationBehavior open func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { accessibilityHandler?.onPageNew(rootMolecules: rootMolecules, delegateObject) } - //MARK: - PageVisibiltyBehaviour open func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { accessibilityHandler?.willShowPage(delegateObject) } diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index 3c3fdbce..916b4c8d 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -9,11 +9,13 @@ import Foundation import Combine +//MARK: - RotorType that our app supports fileprivate enum RotorType: String, CaseIterable { case button = "Buttons" case header = "Header" case checkbox = "Checkbox" + case link = "Link" var trait: UIAccessibilityTraits { switch self { @@ -21,11 +23,14 @@ fileprivate enum RotorType: String, CaseIterable { return .button case .header: return .header + case .link: + return .link default: return .none } } + ///Filter block on model elements based on rotor type var modelFilter: ((MoleculeModelProtocol) -> Bool) { switch self { case .checkbox: @@ -35,6 +40,7 @@ fileprivate enum RotorType: String, CaseIterable { } } + ///Filter block on model UIElements based on rotor type var filter: ((UIView) -> Bool) { switch self { case .checkbox: @@ -44,6 +50,7 @@ fileprivate enum RotorType: String, CaseIterable { } } + ///Returns custom rotor of the specific type func getUIAccessibilityCustomRotor(itemSearch: @escaping UIAccessibilityCustomRotor.Search) -> UIAccessibilityCustomRotor? { var accessibilityCustomRotor: UIAccessibilityCustomRotor? switch self { @@ -56,12 +63,14 @@ fileprivate enum RotorType: String, CaseIterable { } } +//MARK: - RotorViewElementsProtocol public protocol RotorViewElementsProtocol: MVMCoreViewControllerProtocol { var topView: UIView? { get set } var middleView: UIView? { get set } var bottomView: UIView? { get set } } +//MARK: - RotorProtocols public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol { func scrollRectToVisible(_ rect: CGRect, animated: Bool) } @@ -72,11 +81,12 @@ public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol { func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? } +//MARK: - Rotor Element - RotorElement info when we are traversing on model elements in the page. struct RotorElement { let indexPath: IndexPath? let model: MoleculeModelProtocol - let carouselItemModel: MoleculeModelProtocol? + let carouselItemModel: MoleculeModelProtocol?///This element is the parent of model item if we have rotor element inside carousel. This is used to scroll to specific carousel item when rotor mode is enabled. init(indexPath: IndexPath? = nil, model: MoleculeModelProtocol, carouselItemModel: MoleculeModelProtocol? = nil) { self.indexPath = indexPath @@ -85,6 +95,7 @@ struct RotorElement { } } +//MARK: - Rotor Handler class RotorHandler { private var rotorIndexes: [RotorType: Int] = [:] @@ -99,6 +110,7 @@ class RotorHandler { registerForVoiceOverChanges() } + ///Preparing rotors when accessibility voiceover is turned after page is loaded. private func registerForVoiceOverChanges() { NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) .sink { [weak self] _ in @@ -109,6 +121,7 @@ class RotorHandler { }.store(in: &anyCancellable) } + //MARK: - Pagevisibility behaviour methods. public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { rotorIndexes = [:] rotorElements = [:] @@ -119,6 +132,11 @@ class RotorHandler { identifyAndPrepareRotors(delegateObject) } + //MARK: - Rotor methods + /** + This method prepares custom rotors that will be assigned to the current controller. + Rotor will be created only if the page contains that trait mapped element or the conditions met. + */ private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) { guard UIAccessibility.isVoiceOverRunning, let currentViewController = (delegateObject?.moleculeDelegate as? (MVMCoreViewControllerProtocol & UIViewController)) else { return } @@ -134,6 +152,9 @@ class RotorHandler { currentViewController.view.accessibilityCustomRotors = customRotors } + /** + This method prepares trait mapped elements of the current controller and from its manager if exists. + */ private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { var accessibilityElements: [Any?] = [] if let manager = (template as? MVMCoreViewManagerViewControllerProtocol)?.manager as? (MVMCoreViewControllerProtocol & UIViewController) { @@ -148,11 +169,19 @@ class RotorHandler { return accessibilityElements.compactMap { $0 } } + /** + This method prepares trait mapped elements from manager + */ private func getRotorElementsFrom(manager: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { guard let elements = (manager as? MVMCoreViewManagerProtocol)?.getAccessibilityElements() as? [UIView] else { return nil } return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: elements).filter { type.filter($0) } as [Any] } + /** + This method prepares triat mapped elements from the current viewcontroller. + For BAU pages: This will traverse through the view hierarchy for trait mapped elements. + For Molecular pages: This will traverse through the models to identify the trait mapped model. Along with traversed models, again we are traversing on view hierarchy if any subViews are added to view to get trait mapped elements. + */ private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & ViewController) else { return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { type.filter($0) } as [Any] //BAU pages @@ -162,6 +191,10 @@ class RotorHandler { return rotorElements + remainingRotorElements } + /** + This method is to get rotor elements form Molecular pages only. + We are filtering out header, molecules, footer from rootMolcules because rootMolcules may or maynot have the correct order of molecule models(header, body, footer). If we don't maintain order, Voiceover might first go footer then body elements then header.(Safety step) + */ private func getRotorElements(from template: (MVMCoreViewControllerProtocol & ViewController & RotorViewElementsProtocol)?, type: RotorType) -> [Any] { guard let templateModel = template?.model as? ThreeLayerModelBase else { return [] } var rotorElements: [Any] = [] @@ -176,6 +209,11 @@ class RotorHandler { return rotorElements } + /** + This method actually travers through the molecules and identify triat mapped model element along with indexPath of the element if its List/Collection templates. + Along with model, indexPath we are also capturing carouselItemModel because when we have Carousel inside the molecule, we need to scroll to carousel item if we have trait mapped rotor element inside the Carousel - (Multi Scroll behaviour) + Identiying the CarouselModel, CarouselItemModel by depth. + */ private func getRotorElements(from molecules: [MoleculeModelProtocol], template: (MVMCoreViewControllerProtocol & ViewController)?, type: RotorType) -> [Any] { var traitIndexPath: IndexPath? var carouselItemModel: MoleculeModelProtocol? @@ -200,20 +238,37 @@ class RotorHandler { return rotorElements as [Any] } + /** + This method creates a rotor based on the RotorType. + UIAccessibilityCustomRotor.Search Predicate block is used to return the current rotor UI element + If the rotor element is of type UIElement(subclasses of UIView) then it will post the accessibility notification directly to that UI element and scrolling that element to the visible area. + If rotor element is of type RotorElement then + 1. If we have indexPath in rotorElement then the template will scroll (list/collection) to that indexPath, and try to capture the cell view. + 2. After the cell view is captured, traversing the cell hierarchy which matches the trait & id of that view's model. + 3. After identifying the element, then will post the accessibility notification directly to that UI element + If we have carouselItemModel in RotorElement then + 1. If we have indexPath in rotorElement then the template will scroll (list/collection) to that indexPath, and try to capture the cell view. + 2. After cell is identified then we are identifying Carousel from the view hierarchy & scroll to the Carousel item which matches the carouselItemModel.id + 3. After carouselItemModel is scrolled then traversing the carouselCellItem hierarchy which matches the trait & id of that view's model + 4. After identifying the element, then will post the accessibility notification directly to that UI element + */ private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? { return type.getUIAccessibilityCustomRotor { [weak self] predicate in guard let self, let elements = self.rotorElements[type] else { return UIAccessibilityCustomRotorItemResult() } var rotorIndex = self.rotorIndexes[type] ?? -1 - if predicate.searchDirection == .next { + switch predicate.searchDirection { + case .next: if rotorIndex + 1 < elements.count { rotorIndex += 1 } - } else { + case .previous: if rotorIndex > 0 { rotorIndex -= 1 } + @unknown default: + rotorIndex = 0 } - guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() } + guard rotorIndex >= 0, !elements.isEmpty else { return UIAccessibilityCustomRotorItemResult() } //Safety check to avoid crash. var rotorElement = elements[rotorIndex] if let element = rotorElement as? RotorElement, let controller = self.delegate as? (RotorViewElementsProtocol & ViewController) { @@ -251,6 +306,7 @@ class RotorHandler { } } +//MARK: - Protocol Extensions public extension RotorViewElementsProtocol { var topView: UIView? { nil } var middleView: UIView? { nil } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index ea7d89b8..6b360120 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -384,7 +384,7 @@ open class Carousel: View { extension Carousel: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = collectionView.bounds.width * itemWidthPercent + let itemWidth = (collectionView.bounds.width - collectionView.contentInset.left - collectionView.contentInset.right) * itemWidthPercent return CGSize(width: itemWidth, height: collectionView.bounds.height) } From 82ba81960cf1d61f2d0db91f4cdf08cec493e624 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Fri, 20 Oct 2023 00:12:05 +0530 Subject: [PATCH 077/112] ListStoreLocator component enhancement Updated heart with badge as per new design --- .../List/Miscellaneous/ListStoreLocator.swift | 126 ++++-------------- .../Miscellaneous/ListStoreLocatorModel.swift | 12 +- 2 files changed, 33 insertions(+), 105 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift index 1e271e87..5ff57919 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocator.swift @@ -11,149 +11,77 @@ // MARK: - Outlets //-------------------------------------------------- - public let heart = Heart() - public let leftHeadline = Label(fontStyle: .BoldBodySmall) - public let leftBody = Label(fontStyle: .RegularBodySmall) + public let badge = Badge() + public let leftHeadline = Label(fontStyle: .BoldTitleSmall) + public let leftBody = Label(fontStyle: .RegularMicro) public let leftSubBody = Label(fontStyle: .RegularBodySmall) public let rightLabel = Label(fontStyle: .RegularBodySmall) - private lazy var rightLabelStackItem: StackItem = { - StackItem(andContain: rightLabel) - }() + public var model: ListStoreLocatorModel? public lazy var horizontalStack: Stack = { return Stack(with: StackModel(molecules: [StackItemModel(horizontalAlignment: .fill), - StackItemModel(horizontalAlignment: .fill), - StackItemModel(horizontalAlignment: .trailing)], - axis: .horizontal, spacing: Padding.Two), stackItems: [StackItem(andContain: leftHeadline), StackItem(andContain: heart), rightLabelStackItem]) + StackItemModel(horizontalAlignment: .trailing, verticalAlignment: .center)], + axis: .horizontal, spacing: Padding.Two), stackItems: [StackItem(andContain: stack), StackItem(andContain: rightLabel)]) }() + public lazy var stack: Stack = { - return Stack.createStack(with: [horizontalStack, leftBody, leftSubBody], axis: .vertical, spacing: 0) - }() - public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: 12, standardiPadPortraitSize: 18) + return Stack(with: .init(molecules: [StackItemModel(horizontalAlignment: .leading), + StackItemModel(horizontalAlignment: .fill), StackItemModel(horizontalAlignment: .fill), StackItemModel(horizontalAlignment: .fill)], + axis: .vertical, spacing: Padding.One), stackItems: [StackItem(andContain: badge), StackItem(andContain: leftHeadline), StackItem(andContain: leftBody), StackItem(andContain: leftSubBody)]) + + }() //------------------------------------------------------- // MARK: - Lifecycle //------------------------------------------------------- open override func setupView() { super.setupView() - + rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) - addMolecule(stack) + addMolecule(horizontalStack) stack.restack() horizontalStack.restack() + leftSubBody.textColor = UIColor.mfTextLightGray() } - public override func updateView(_ size: CGFloat) { - super.updateView(size) - if let dimension = sizeObject?.getValueBased(onSize: size) { - heart.widthConstraint?.constant = dimension - } - } - //------------------------------------------------------ // MARK: - Molecule //------------------------------------------------------ open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) - guard let model = model as? ListStoreLocatorModel else { return } - horizontalStack.updateContainedMolecules(with: [model.leftHeadline, model.heart, model.rightLabel], delegateObject, additionalData) - leftBody.set(with: model.leftBody, delegateObject, additionalData) - leftSubBody.set(with: model.leftSubBody, delegateObject, additionalData) + stack.updateContainedMolecules(with: [model.badge, model.leftHeadline, model.leftBody, model.leftSubBody], delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } - open override func alignAccessoryToHero() -> CGPoint? { - let heroCenter = super.alignAccessoryToHero() - - if let heroCenter = heroCenter { - let convertedPoint = horizontalStack.convert(heroCenter, from: self) - rightLabelStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - horizontalStack.bounds.midY - } - return heroCenter - } - - public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - if listItemModel?.action != nil { - super.didSelectCell(at: index, delegateObject: delegateObject, additionalData: additionalData) - } else { - heart.tapAction() - updateAccessibilityLabel() - } - } - open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 120 } open override func reset() { super.reset() - leftHeadline.setFontStyle(.BoldBodySmall) - leftBody.setFontStyle(.RegularBodySmall) + leftHeadline.setFontStyle(.BoldTitleSmall) + leftBody.setFontStyle(.RegularMicro) leftSubBody.setFontStyle(.RegularBodySmall) rightLabel.setFontStyle(.RegularBodySmall) + leftSubBody.textColor = UIColor.mfTextLightGray() } - //-------------------------------------------------- - // MARK: - Accessibility - //-------------------------------------------------- - - func getAccessibilityMessage() -> String? { - var message = "" - heart.updateAccessibilityLabel() - - if let leftHeadlineText = leftHeadline.text, !leftHeadlineText.isEmpty { - message += leftHeadlineText + ", " - } - - if let leftBodyText = leftBody.text, !leftBodyText.isEmpty { - message += leftBodyText + ", " - } - - if let leftSubBodyText = leftSubBody.text, !leftSubBodyText.isEmpty { - message += leftSubBodyText + ", " - } - - if let rightLabelText = rightLabel.text, !rightLabelText.isEmpty { - message += rightLabelText - } - return message.count > 0 ? message : nil - } - - func updateAccessibilityLabel() { - let hasHeart = !(horizontalStack.stackModel?.molecules[1].gone ?? true) - if let accessoryView = accessoryView, - hasHeart { - // Both accessory and heart actions. - isAccessibilityElement = false - accessoryView.accessibilityLabel = getAccessibilityMessage() - accessibilityElements = [accessoryView, heart] - } else { - // Make whole cell focusable if no action. - isAccessibilityElement = true - var message = getAccessibilityMessage() - if hasHeart { - accessibilityHint = heart.accessibilityHint - if let heartLabel = heart.accessibilityLabel { - message = (message ?? "") + ", " + heartLabel - } - } else { - accessibilityHint = nil - } - accessibilityLabel = message - } - } - - // Ensures voice over does not read "selected" after user triggers action on cell. override public var accessibilityTraits: UIAccessibilityTraits { get { if (accessoryView != nil) { return .button - } else if (!(horizontalStack.stackModel?.molecules[1].gone ?? true)) { - return heart.accessibilityTraits } else { return .none } } set { } } + + func updateAccessibilityLabel() { + isAccessibilityElement = true + let message = [model?.badge?.text, model?.leftHeadline.text, model?.leftBody.text, model?.leftSubBody.text, model?.rightLabel.text].compactMap { $0 }.joined(separator: ", ") + accessibilityLabel = message + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift index 44c4ef90..912d8877 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Miscellaneous/ListStoreLocatorModel.swift @@ -12,7 +12,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- public static var identifier = "listStoreLocator" - public var heart: HeartModel? + public var badge: BadgeModel? public var leftHeadline: LabelModel public var leftBody: LabelModel public var leftSubBody: LabelModel @@ -22,8 +22,8 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(heart: HeartModel?, leftHeadline: LabelModel, leftBody: LabelModel, leftSubBody: LabelModel, rightLabel: LabelModel) { - self.heart = heart + public init(badge: BadgeModel?, leftHeadline: LabelModel, leftBody: LabelModel, leftSubBody: LabelModel, rightLabel: LabelModel) { + self.badge = badge self.leftHeadline = leftHeadline self.leftBody = leftBody self.leftSubBody = leftSubBody @@ -49,7 +49,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { private enum CodingKeys: String, CodingKey { case moleculeName - case heart + case badge case leftHeadline case leftBody case leftSubBody @@ -62,7 +62,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - heart = try typeContainer.decodeIfPresent(HeartModel.self, forKey:.heart) + badge = try typeContainer.decodeIfPresent(BadgeModel.self, forKey:.badge) leftHeadline = try typeContainer.decode(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decode(LabelModel.self, forKey: .leftBody) leftSubBody = try typeContainer.decode(LabelModel.self, forKey: .leftSubBody) @@ -74,7 +74,7 @@ public class ListStoreLocatorModel: ListItemModel, MoleculeModelProtocol { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(heart, forKey: .heart) + try container.encodeIfPresent(badge, forKey: .badge) try container.encode(leftHeadline, forKey: .leftHeadline) try container.encode(leftBody, forKey: .leftBody) try container.encode(leftSubBody, forKey: .leftSubBody) From b1da7aaeed03b28035f7c95d843a823647185ccd Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 23 Oct 2023 16:41:08 -0400 Subject: [PATCH 078/112] Footer button filled for iphones --- .../Atomic/Molecules/HeadersAndFooters/FooterModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift index bab51c2d..dd23035c 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift @@ -24,5 +24,9 @@ if bottomPadding == nil { bottomPadding = PaddingDefaultVerticalSpacing } + guard let _ = molecule as? ButtonModel, + !MVMCoreGetterUtility.isOnIPad(), + horizontalAlignment == nil else { return } + horizontalAlignment = .fill } } From e373aa3b086c8ec4128691319fa50eb77b7339fc Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 23 Oct 2023 17:42:32 -0400 Subject: [PATCH 079/112] Full width foot pill button --- .../Molecules/HeadersAndFooters/FooterModel.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift index dd23035c..93e296f2 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/FooterModel.swift @@ -24,9 +24,14 @@ if bottomPadding == nil { bottomPadding = PaddingDefaultVerticalSpacing } - guard let _ = molecule as? ButtonModel, - !MVMCoreGetterUtility.isOnIPad(), - horizontalAlignment == nil else { return } - horizontalAlignment = .fill + guard !MVMCoreGetterUtility.isOnIPad(), + horizontalAlignment == nil else { return } + + if let _ = molecule as? ButtonModel { + horizontalAlignment = .fill + } else if let model = molecule as? TwoButtonViewModel, + model.primaryButton == nil || model.secondaryButton == nil { + horizontalAlignment = .fill + } } } From 0c4136a6b12ee0b79407f9797460c10e38e2537d Mon Sep 17 00:00:00 2001 From: "Bruce, Matt R" Date: Thu, 26 Oct 2023 15:08:24 +0000 Subject: [PATCH 080/112] removed comment --- MVMCoreUI/Atomic/Atoms/Views/Badge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift index 1306074f..5e8c2e25 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Badge.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Badge.swift @@ -12,7 +12,7 @@ import VDS import Combine open class Badge: VDS.Badge, VDSMoleculeViewProtocol { - // public typealias ViewModel = type + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- From 31a1df7239fe8f8424512fb4f295d6aa1b2ae32c Mon Sep 17 00:00:00 2001 From: "Bruce, Matt R" Date: Thu, 26 Oct 2023 16:27:59 +0000 Subject: [PATCH 081/112] reorder badge/tilelet --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index d6404d24..0f1302b1 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -295,8 +295,8 @@ AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; - B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; }; B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; }; + B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; }; @@ -573,8 +573,8 @@ EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; }; EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */; }; - EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3D2970938F00F2FF2E /* Tilelet.swift */; }; EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3F2970939A00F2FF2E /* TileletModel.swift */; }; + EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3D2970938F00F2FF2E /* Tilelet.swift */; }; EA985C602970A3F000F2FF2E /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C5F2970A3F000F2FF2E /* VDS.framework */; }; EA985C642970A40E00F2FF2E /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */; }; EA985C852981AA9C00F2FF2E /* VDS-Enums+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C842981AA9C00F2FF2E /* VDS-Enums+Codable.swift */; }; From 5d08807d5d9d7b1561315fff5e80821578cf9a6d Mon Sep 17 00:00:00 2001 From: "Bruce, Matt R" Date: Thu, 26 Oct 2023 16:33:11 +0000 Subject: [PATCH 082/112] Update File order --- MVMCoreUI.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 0f1302b1..4511d436 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -883,8 +883,8 @@ AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; - B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = ""; }; + B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; @@ -2219,10 +2219,10 @@ AA37CBD42519072F0027344C /* Stars.swift */, AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, - B4CC8FBC29DF34680005D28B /* Badge.swift */, B4CC8FBE29DF34730005D28B /* BadgeModel.swift */, - EA985C3D2970938F00F2FF2E /* Tilelet.swift */, + B4CC8FBC29DF34680005D28B /* Badge.swift */, EA985C3F2970939A00F2FF2E /* TileletModel.swift */, + EA985C3D2970938F00F2FF2E /* Tilelet.swift */, ); path = Views; sourceTree = ""; From 1eb0103744c8458c23c8a4cbf251092e5a661ef8 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 26 Oct 2023 16:39:30 -0400 Subject: [PATCH 083/112] fix stack overflow error on RawRepresentable encoding --- MVMCoreUI/Utility/RawRepresentableCodable.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MVMCoreUI/Utility/RawRepresentableCodable.swift b/MVMCoreUI/Utility/RawRepresentableCodable.swift index 09441add..128c21c2 100644 --- a/MVMCoreUI/Utility/RawRepresentableCodable.swift +++ b/MVMCoreUI/Utility/RawRepresentableCodable.swift @@ -32,9 +32,4 @@ extension RawRepresentableCodable { throw RawRepresentableCodableError.invalid(value: "\(rawValue)") } } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self) - } } From e48db9495d4e58e4037efc51b4e24457bce20af9 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Mon, 30 Oct 2023 13:54:48 +0530 Subject: [PATCH 084/112] Minor refactoring --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 29b727b6..73047b06 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -24,9 +24,9 @@ public class AccessibilityOperation: MVMCoreOperation { /** This method will post accessibility notification. - If we have announcement notification then operation will wait untill announcement is finished. */ public override func main() { + guard !checkAndHandleForCancellation() else { return } guard UIAccessibility.isVoiceOverRunning else { markAsFinished() return @@ -37,13 +37,7 @@ public class AccessibilityOperation: MVMCoreOperation { return } UIAccessibility.post(notification: self.notificationType, argument: self.argument) - if self.notificationType == .announcement {///Marking task as finished only if announcement did finished so that user will listen complete announcement. - NotificationCenter.default.addObserver(forName: UIAccessibility.announcementDidFinishNotification, object: nil, queue: .main) { _ in - self.markAsFinished() - } - } else { - self.markAsFinished() - } + self.markAsFinished() } } } @@ -131,16 +125,12 @@ extension AccessibilityHandler { /** This method is used to notify rotor handler about page loaded and capturing the accessibilityId if any. - Will announce text if the page is has announcementText in the response. */ public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { previousAccessiblityElement = nil rotorHandler.onPageNew(rootMolecules: rootMolecules, delegateObject) guard let loadObject = (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.loadObject else { return } accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId") - if let announcementText = loadObject?.pageJSON?.optionalStringForKey("announcementText") { - post(notification: .announcement, argument: announcementText) - } self.delegateObject = delegateObject } From 49d6d2bf76a7c6381f6cb5a6b840a8b52b8972f3 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 30 Oct 2023 12:45:12 -0400 Subject: [PATCH 085/112] bugfix for old color properties --- .../Atomic/Atoms/Buttons/ButtonModel.swift | 195 ++++-------------- .../NotificationMoleculeModel.swift | 6 +- 2 files changed, 43 insertions(+), 158 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 94a7778b..38abd4cd 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -27,54 +27,12 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var action: ActionModelProtocol public var enabled: Bool = true public var width: CGFloat? - public var style: Use? { - didSet { - guard let style = style else { return } - setFacade(by: style) - } - } + public var style: Use = .primary public var size: VDS.Button.Size = .large public var groupName: String = "" public var inverted: Bool = false - - public lazy var enabledColors: FacadeElements = (fill: enabled_fillColor(), - text: enabled_textColor(), - border: enabled_borderColor()) - - public lazy var disabledColors: FacadeElements = (fill: disabled_fillColor(), - text: disabled_textColor(), - border: disabled_borderColor()) - - public var enabledFillColor: Color? - public var enabledTextColor: Color? - public var enabledBorderColor: Color? - - public var enabledFillColor_inverted: Color? - public var enabledTextColor_inverted: Color? - public var enabledBorderColor_inverted: Color? - - public var disabledFillColor: Color? - public var disabledTextColor: Color? - public var disabledBorderColor: Color? - - public var disabledFillColor_inverted: Color? - public var disabledTextColor_inverted: Color? - public var disabledBorderColor_inverted: Color? - - private var _backgroundColor: Color? - public var backgroundColor: Color? { - get { - if let backgroundColor = _backgroundColor { return backgroundColor } - if inverted { - return enabled ? enabledFillColor_inverted : disabledFillColor_inverted - } - return enabled ? enabledFillColor : disabledFillColor - } - set { - _backgroundColor = newValue - } - } - + public var backgroundColor: Color? + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -88,88 +46,18 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public init(with title: String, action: ActionModelProtocol) { self.title = title self.action = action - setFacade(by: .primary) } public init(secondaryButtonWith title: String, action: ActionModelProtocol) { self.title = title self.action = action style = .secondary - setFacade(by: .secondary) } public init(primaryButtonWith title: String, action: ActionModelProtocol) { self.title = title self.action = action style = .primary - setFacade(by: .primary) - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - public func enabled_fillColor() -> UIColor? { - (inverted ? enabledFillColor_inverted : enabledFillColor)?.uiColor - } - - public func enabled_textColor() -> UIColor? { - (inverted ? enabledTextColor_inverted : enabledTextColor)?.uiColor - } - - public func enabled_borderColor() -> UIColor? { - (inverted ? enabledBorderColor_inverted : enabledBorderColor)?.uiColor - } - - public func disabled_fillColor() -> UIColor? { - (inverted ? disabledFillColor_inverted : disabledFillColor)?.uiColor - } - - public func disabled_textColor() -> UIColor? { - (inverted ? disabledTextColor_inverted : disabledTextColor)?.uiColor - } - - public func disabled_borderColor() -> UIColor? { - (inverted ? disabledBorderColor_inverted : disabledBorderColor)?.uiColor - } - - /// Defines the default appearance for the primary style. - func setPrimaryFacade() { - enabledFillColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) - enabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOndark) - disabledFillColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) - disabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOndark) - - enabledFillColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) - enabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOnlight) - disabledFillColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) - disabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOnlight) - } - - /// Defines the default appearance for the Secondary style. - func setSecondaryFacade() { - enabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) - enabledFillColor = Color(uiColor: UIColor.clear) - enabledBorderColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) - disabledTextColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) - disabledBorderColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) - - enabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) - enabledFillColor_inverted = Color(uiColor: UIColor.clear) - enabledBorderColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) - disabledTextColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) - disabledBorderColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) - } - - public func setFacade(by style: VDS.Use) { - switch style { - case .primary: - setPrimaryFacade() - case .secondary: - setSecondaryFacade() - @unknown default: - setPrimaryFacade() - } } //-------------------------------------------------- @@ -179,7 +67,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat private enum CodingKeys: String, CodingKey { case id case moleculeName - case backgroundColor case accessibilityIdentifier case accessibilityText case title @@ -189,12 +76,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case style case size case groupName - case fillColor - case textColor - case borderColor - case disabledFillColor - case disabledTextColor - case disabledBorderColor case width } @@ -214,12 +95,8 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat ///Style captured from the JSON if let style = try typeContainer.decodeIfPresent(Use.self, forKey: .style) { self.style = style - setFacade(by: style) } else if let style = decoder.context?.value(forKey: CodingKeys.style.stringValue) as? Use { ///Reading the style param from context which is set is molecules, ex: TwoButtonView self.style = style - setFacade(by: style) - } else { ///Default style - setFacade(by: .primary) } if let size = try typeContainer.decodeIfPresent(VDS.Button.Size.self, forKey: .size) { @@ -230,40 +107,57 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat self.enabled = enabled } - if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { - self.inverted = inverted - } - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) + + if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + self.inverted = inverted + } else { + try setInverted(deprecatedFrom: decoder) + } + } + + private enum DeprecatedCodingKeys: String, CodingKey { + case fillColor + case textColor + case borderColor + case backgroundColor + } + + private func setInverted(deprecatedFrom decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self) if let enabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) { - self.enabledFillColor = enabledFillColor + if (self.style == .secondary) { + self.inverted = enabledFillColor.uiColor.isDark() + } else { + self.inverted = !enabledFillColor.uiColor.isDark() + } } if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) { - self.enabledTextColor = enabledTextColor + if (self.style == .secondary) { + self.inverted = !enabledTextColor.uiColor.isDark() + } else { + self.inverted = enabledTextColor.uiColor.isDark() + } } if let enabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { - self.enabledBorderColor = enabledBorderColor + if (self.style == .secondary) { + self.inverted = !enabledBorderColor.uiColor.isDark() + } } - - if let disabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledFillColor) { - self.disabledFillColor = disabledFillColor + + if let backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { + if (self.style == .secondary) { + self.inverted = backgroundColor.uiColor.isDark() + } else { + self.inverted = !backgroundColor.uiColor.isDark() + } } - - if let disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) { - self.disabledTextColor = disabledTextColor - } - - if let disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) { - self.disabledBorderColor = disabledBorderColor - } - - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } open func encode(to encoder: Encoder) throws { @@ -274,15 +168,8 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encode(enabled, forKey: .enabled) try container.encode(inverted, forKey: .inverted) try container.encodeModel(action, forKey: .action) - try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) - try container.encodeIfPresent(enabledFillColor, forKey: .fillColor) - try container.encodeIfPresent(enabledTextColor, forKey: .textColor) - try container.encodeIfPresent(enabledBorderColor, forKey: .borderColor) - try container.encodeIfPresent(disabledFillColor, forKey: .disabledFillColor) - try container.encodeIfPresent(disabledTextColor, forKey: .disabledTextColor) - try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor) try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(size, forKey: .size) try container.encodeIfPresent(groupName, forKey: .groupName) diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift index 27ca95a8..9958b59a 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationMoleculeModel.swift @@ -93,11 +93,9 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol { button?.style = .secondary switch style { case .error, .warning: - button?.enabledTextColor = Color(uiColor: .mvmBlack) - button?.enabledBorderColor = Color(uiColor: .mvmBlack) + button?.inverted = false default: - button?.enabledTextColor = Color(uiColor: .mvmWhite) - button?.enabledBorderColor = Color(uiColor: .mvmWhite) + button?.inverted = true } if closeButton?.color == nil { From 17a16bf73cf12293dc0a298eecb0216a7d7d8268 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 30 Oct 2023 12:47:53 -0400 Subject: [PATCH 086/112] change encode --- MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 38abd4cd..b712b04e 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -170,8 +170,8 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encodeModel(action, forKey: .action) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) - try container.encodeIfPresent(style, forKey: .style) - try container.encodeIfPresent(size, forKey: .size) + try container.encode(style, forKey: .style) + try container.encode(size, forKey: .size) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(width, forKey: .width) } From 4b9c3860716e0856476108f38813d7c363f9683b Mon Sep 17 00:00:00 2001 From: "Rajendran, Nandhini" Date: Tue, 31 Oct 2023 15:30:39 +0000 Subject: [PATCH 087/112] story: ONEAPP-5868 page control update --- .../Atoms/Views/CarouselIndicator/BarsIndicatorView.swift | 4 +++- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift index 2eefd716..4cff7bdb 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift @@ -213,7 +213,9 @@ open class BarsIndicatorView: CarouselIndicator { let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1)) else { return } - view.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages) + let accessibilityValue = String(format: accessibleValueFormat, accessibleIndex, numberOfPages) + view.accessibilityLabel = accessibilityValue + view.accessibilityIdentifier = accessibilityValue } public override func assessTouchOf(_ touchPoint_X: CGFloat) { diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 6b360120..cb618524 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -372,7 +372,9 @@ open class Carousel: View { self.carouselAccessibilityElement = carouselAccessibilityElement } - if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { + if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), let pagingView = self.pagingView { + _accessibilityElements = [currentCell, carouselAccessibilityElement, pagingView] + } else if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { _accessibilityElements = [currentCell, carouselAccessibilityElement] } else { _accessibilityElements = [carouselAccessibilityElement] From 9be7fef53c2ae991c19bba1d8e97caf127e25182 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 31 Oct 2023 22:51:39 +0530 Subject: [PATCH 088/112] Showing/Hiding the text while starting/pausing the loading overlay --- MVMCoreUI/BaseControllers/MFLoadingViewController.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MVMCoreUI/BaseControllers/MFLoadingViewController.m b/MVMCoreUI/BaseControllers/MFLoadingViewController.m index fb7dd8f8..79565df2 100644 --- a/MVMCoreUI/BaseControllers/MFLoadingViewController.m +++ b/MVMCoreUI/BaseControllers/MFLoadingViewController.m @@ -45,6 +45,7 @@ Label *infoLabel = [Label label]; infoLabel.textAlignment = NSTextAlignmentCenter; infoLabel.translatesAutoresizingMaskIntoConstraints = NO; + infoLabel.hidden = true; self.indicatorText = infoLabel; [loadingStack addArrangedSubview:infoLabel]; @@ -84,15 +85,20 @@ if(text != nil){ self.indicatorText.attributedText = text; self.indicatorText.accessibilityLabel = text.string; + self.indicatorText.hidden = false; } else { self.indicatorText.attributedText = nil; self.indicatorText.accessibilityLabel = @""; + self.indicatorText.hidden = true; } [self.activityIndicator resumeSpinner]; } - (void)stopLoading { [self.activityIndicator pauseSpinner]; + self.indicatorText.hidden = true; + self.indicatorText.attributedText = nil; + self.indicatorText.accessibilityLabel = @""; } @end From 24c684d64ff99ee327f027c4cd398433b08134ae Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 1 Nov 2023 00:06:41 +0530 Subject: [PATCH 089/112] Updated accessibilityBehavior --- .../FormFields/TextFields/TextEntryField.swift | 2 +- .../Atomic/Atoms/Views/CheckboxLabel.swift | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 18749c8a..ad2187a1 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -379,7 +379,7 @@ import UIKit if let text = model.text, !text.isEmpty { regexTextFieldOutputIfAvailable() } - + setAccessibilityString(model.title ?? "") } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift index 88ee69be..2bfd7e28 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift @@ -14,6 +14,7 @@ public let checkbox = Checkbox() public let label = Label(fontStyle: .RegularBodySmall) + private var observation: NSKeyValueObservation? = nil //-------------------------------------------------- // MARK: - Properties @@ -63,6 +64,12 @@ bottomLabelConstraint.isActive = true alignCheckbox(.center) + isAccessibilityElement = true + accessibilityHint = checkbox.accessibilityHint + accessibilityTraits = checkbox.accessibilityTraits + observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in + self?.updateAccessibilityLabel() + } } @objc override open func updateView(_ size: CGFloat) { @@ -111,6 +118,7 @@ checkbox.set(with: checkBoxWithLabelModel.checkbox, delegateObject, additionalData) label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { @@ -124,4 +132,13 @@ checkbox.reset() alignCheckbox(.center) } + + override open func accessibilityActivate() -> Bool { + checkbox.accessibilityActivate() + } + + open func updateAccessibilityLabel() { + checkbox.updateAccessibilityLabel() + accessibilityLabel = [label.text, checkbox.accessibilityLabel].compactMap { $0 }.joined(separator: ",") + } } From 92b7c3ef363cbc4dc047fd724b95934c0eae6e15 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 1 Nov 2023 11:19:30 +0530 Subject: [PATCH 090/112] foucs spelling mistake --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 73047b06..d8053cf9 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -45,7 +45,7 @@ public class AccessibilityOperation: MVMCoreOperation { //MARK: - AccessibilityHandler /** AccessibilityHandler will observe the page visibility of every view controller and post notification to the first interactive element on the screen. - If we have to shift/foucs custom element on the screen on controller shown then we need to pass accessibilityId in the page response. + If we have to shift/focus custom element on the screen on controller shown then we need to pass accessibilityId in the page response. */ open class AccessibilityHandler { @@ -75,7 +75,7 @@ open class AccessibilityHandler { } /** - This method will capture current foucsed element + This method will capture current focused element */ open func capturePreviousFocusElement() { previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver) @@ -138,7 +138,7 @@ extension AccessibilityHandler { This method is used to capture accessibility views on the screen. If the page has accessibilityId, then it will not post any accessibility notification because respective UI mapped element can be identified only on page shown. If it has top notification then we are capturing the first focused element and will not post any accessibility notification. - If page doesn't have any top notification or accessibilityId then it will post notification to shift foucs to first focused element on the screen. + If page doesn't have any top notification or accessibilityId then it will post notification to shift focus to first focused element on the screen. */ public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) { updateAccessibilityViews(delegateObject) @@ -201,8 +201,8 @@ extension AccessibilityHandler { @objc extension AccessibilityHandler { /** - This method listens for foucs changes. - When foucs is changed manually then we are cancelling existing operations. + This method listens for focus changes. + When focus is changed manually then we are cancelling existing operations. */ private func registerForFocusChanges() { //Since focus shifted to other elements cancelling existing focus shift notifications if any From 96fa0bf31442dd028ab8774681772740d89a9b9d Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 1 Nov 2023 13:47:23 +0530 Subject: [PATCH 091/112] Removed accessibilityElements to resolve focus stuck issue --- MVMCoreUI/Atomic/Organisms/Stack.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index a2a07953..d5de925c 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -48,15 +48,6 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } isAccessibilityElement = false - // TODO: need more investigation - /* var accessibleViews: [Any] = [] - - for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { - accessibleViews.append(view) - } - - accessibilityElements = accessibleViews - */ } /// Removes all stack items views from the view. From 34dcc80f6782cb330924a52d6a77297a5b7ab444 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 1 Nov 2023 14:09:55 +0530 Subject: [PATCH 092/112] Updated linkShowing logic for accessibility --- .../ListLeftVariableCheckboxAllTextAndLinks.swift | 5 +++-- .../List/LeftVariable/ListLeftVariableIconAllTextLinks.swift | 4 +++- .../ListLeftVariableIconWithRightCaretAllTextLinks.swift | 5 +++-- .../ListLeftVariableNumberedListAllTextAndLinks.swift | 4 +++- .../ListLeftVariableRadioButtonAllTextAndLinks.swift | 5 +++-- .../ListLeftVariableRadioButtonAndPaymentMethod.swift | 5 +++-- .../ListOneColumnFullWidthTextAllTextAndLinks.swift | 4 +++- .../ListRightVariablePriceChangeAllTextAndLinks.swift | 4 +++- .../ListRightVariableRightCaretAlltextAndLinks.swift | 4 +++- 9 files changed, 27 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift index 1b5b0697..55ae3e02 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift @@ -20,7 +20,7 @@ //-------------------------------------------------- public var stack: Stack - + public var model: MoleculeModelProtocol? private var observation: NSKeyValueObservation? = nil //-------------------------------------------------- @@ -68,6 +68,7 @@ checkbox.set(with: model.checkbox, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -93,7 +94,7 @@ message += label } - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableCheckboxAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing if !linkShowing { // Make whole cell focusable if no link. diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift index 728d8464..78b91f9f 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift @@ -15,6 +15,7 @@ public let leftImage = LoadImageView(pinnedEdges: .all) public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() public var stack: Stack + public var model: MoleculeModelProtocol? //-------------------------------------------------- // MARK: - Initializers @@ -53,6 +54,7 @@ leftImage.set(with: model.image, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -75,7 +77,7 @@ func updateAccessibilityLabel() { - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableIconAllTextLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinks.swift index 0c09e9e3..683ef1ab 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinks.swift @@ -14,7 +14,7 @@ public let leftImage = LoadImageView(pinnedEdges: .all) public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(spacing: 2.0) public let rightLabel = Label(fontStyle: .RegularBodySmall) - + public var model: MoleculeModelProtocol? public lazy var rightLabelStackItem: StackItem = { return StackItem(andContain: rightLabel) }() @@ -66,6 +66,7 @@ leftImage.set(with: model.image, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -97,7 +98,7 @@ } func updateAccessibilityLabel() { - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableIconWithRightCaretAllTextLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift index 75096854..f3afc623 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift @@ -15,6 +15,7 @@ public let leftLabel = Label(fontStyle: .Title2XLarge) public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() public var stack: Stack + public var model: MoleculeModelProtocol? //-------------------------------------------------- // MARK: - Initializers @@ -54,6 +55,7 @@ leftLabel.text = String(model.number) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -85,7 +87,7 @@ func updateAccessibilityLabel() { - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableNumberedListAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift index 730f988f..cc3e57d6 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift @@ -15,7 +15,7 @@ let radioButton = RadioButton() let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() var stack: Stack - + public var model: MoleculeModelProtocol? private var observation: NSKeyValueObservation? = nil //----------------------------------------------------- @@ -62,6 +62,7 @@ radioButton.set(with: model.radioButton, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -91,7 +92,7 @@ message += label } - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableRadioButtonAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing if !linkShowing { // Make whole cell focusable if no link. diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift index 48e3c49c..b749355d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift @@ -18,7 +18,7 @@ import UIKit let leftImage = LoadImageView(pinnedEdges: .all) let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() var stack: Stack - + public var model: MoleculeModelProtocol? private var observation: NSKeyValueObservation? = nil //----------------------------------------------------- @@ -79,6 +79,7 @@ import UIKit radioButton.set(with: model.radioButton, delegateObject, additionalData) leftImage.set(with: model.image, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -112,7 +113,7 @@ import UIKit message += label } - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListLeftVariableRadioButtonAndPaymentMethodModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing if !linkShowing { // Make whole cell focusable if no link. diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift index 6e5a7245..c2c13089 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift @@ -20,6 +20,7 @@ import Foundation let subHeadline = Label(fontStyle: .BoldBodySmall) let body = Label(fontStyle: .RegularBodySmall) let link = Link() + public var model: MoleculeModelProtocol? //----------------------------------------------------- // MARK: - Initializers @@ -52,6 +53,7 @@ import Foundation stack.updateContainedMolecules(with: [model.eyebrow, model.headline, model.subHeadline, model.body, model.link], delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -102,7 +104,7 @@ import Foundation func updateAccessibilityLabel() { - let linkShowing = link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListOneColumnFullWidthTextAllTextAndLinksModel)?.link?.title != nil isAccessibilityElement = !linkShowing if !linkShowing { // Make whole cell focusable if no link. diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift index d0b2ff70..4194d112 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift @@ -18,6 +18,7 @@ private let stack: Stack private let arrowStackItem: StackItem private let rightLabelStackItem: StackItem + public var model: MoleculeModelProtocol? //----------------------------------------------------- // MARK: - Initializers @@ -73,6 +74,7 @@ eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) arrow.set(with: model.arrow, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -98,7 +100,7 @@ func updateAccessibilityLabel() { - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListRightVariablePriceChangeAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift index 12f11b9e..7048a536 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift @@ -15,6 +15,7 @@ public let rightLabel = Label(fontStyle: .RegularBodySmall) public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() public var stack: Stack + public var model: MoleculeModelProtocol? //-------------------------------------------------- // MARK: - Initializers @@ -65,6 +66,7 @@ guard let model = model as? ListRightVariableRightCaretAllTextAndLinksModel else { return } rightLabel.set(with: model.rightLabel, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + self.model = model updateAccessibilityLabel() } @@ -95,7 +97,7 @@ func updateAccessibilityLabel() { - let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + let linkShowing = (model as? ListRightVariableRightCaretAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil isAccessibilityElement = !linkShowing accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none From 975882d47cbb50d106755532e0287b998d8b58f5 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 1 Nov 2023 18:11:22 +0530 Subject: [PATCH 093/112] addressed code review comments --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 4 +++- MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index d8053cf9..96053673 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -100,9 +100,10 @@ open class AccessibilityHandler { /** This method return first focused element from the screen. + If navigationBar is hidden then we are returning nil so that voice over will shift to the first interactive element. */ open func getFirstFocusedElementOnScreen() -> Any? { - (delegate as? UIViewController)?.navigationController?.navigationBar + ((delegate as? PageProtocol)?.pageModel?.navigationBar?.hidden ?? false) ? nil : (delegate as? UIViewController)?.navigationController?.navigationBar } /** @@ -245,6 +246,7 @@ extension AccessibilityHandler { /** To notify AccessibilityHandler about the page visibility changes */ +//TODO: Revisit why we need a behavior as a notifier. open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTransformationBehavior { public let accessibilityHandler: AccessibilityHandler? diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 008c2911..f4ce9234 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -7,7 +7,7 @@ // -public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Identifiable { +public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- From 848defaf39804b5a7152776205f6fe18e85b5619 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 1 Nov 2023 12:33:03 -0400 Subject: [PATCH 094/112] 32 -> 16 horizontal margin --- MVMCoreUI/Styles/MFStyler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index e65fddc5..c77e447d 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -17,7 +17,7 @@ #import CGFloat const PaddingDefault = 24; -CGFloat const PaddingDefaultHorizontalSpacing = 32; +CGFloat const PaddingDefaultHorizontalSpacing = 16; CGFloat const PaddingDefaultVerticalSpacing = 32; CGFloat const PaddingDefaultVerticalSpacing3 = 24; CGFloat const PaddingBetweenFields = 24; From 5d7dab8879468bee10d92203b70f4dac5c58372d Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 1 Nov 2023 22:51:04 +0530 Subject: [PATCH 095/112] Removed checkbox from rotor --- MVMCoreUI/Accessibility/RotorHandler.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index 916b4c8d..d529564b 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -14,7 +14,6 @@ fileprivate enum RotorType: String, CaseIterable { case button = "Buttons" case header = "Header" - case checkbox = "Checkbox" case link = "Link" var trait: UIAccessibilityTraits { @@ -33,8 +32,6 @@ fileprivate enum RotorType: String, CaseIterable { ///Filter block on model elements based on rotor type var modelFilter: ((MoleculeModelProtocol) -> Bool) { switch self { - case .checkbox: - return { $0 is CheckboxModel } default: return { $0.accessibilityTraits?.contains(trait) ?? false } } @@ -43,8 +40,6 @@ fileprivate enum RotorType: String, CaseIterable { ///Filter block on model UIElements based on rotor type var filter: ((UIView) -> Bool) { switch self { - case .checkbox: - return { $0 is Checkbox } default: return { $0.accessibilityTraits.contains(trait) } } From 9fe6a4dfc1b904d64f67408e6991e51fb6502181 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Wed, 1 Nov 2023 23:59:34 +0530 Subject: [PATCH 096/112] Updated accessibility behavior for TextEntryField, BaseItemPickerEntryField, CheckboxLabel --- .../Item Dropdown/BaseItemPickerEntryField.swift | 7 ++----- .../Atoms/FormFields/TextFields/TextEntryField.swift | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift index fa880a50..65c4448b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift @@ -75,11 +75,8 @@ extension BaseItemPickerEntryField { @objc open override func setAccessibilityString(_ accessibilityString: String?) { var accessibilityString = accessibilityString ?? "" - - if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { - accessibilityString += textPickerItem - } - + textField.accessibilityTraits = .staticText + textField.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index ad2187a1..a40aa56f 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -399,10 +399,6 @@ extension TextEntryField { var accessibilityString = accessibilityString ?? "" - if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { - accessibilityString += txtRegular - } - textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" } } From 5920bf3e50b4f52538982e085d9d1d4f6acdc6ba Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 2 Nov 2023 00:52:13 +0530 Subject: [PATCH 097/112] Order change --- MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift index 2bfd7e28..965656a6 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift @@ -139,6 +139,6 @@ open func updateAccessibilityLabel() { checkbox.updateAccessibilityLabel() - accessibilityLabel = [label.text, checkbox.accessibilityLabel].compactMap { $0 }.joined(separator: ",") + accessibilityLabel = [checkbox.accessibilityLabel, label.text].compactMap { $0 }.joined(separator: ",") } } From df2fa3405b863ed01458a6f53dbfe7f714d6b387 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 2 Nov 2023 18:46:27 +0530 Subject: [PATCH 098/112] Updated delegateObject and delegate properties removed weak ref for delegateObject. updated delegate as delegate.loadDelegate --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 96053673..2cfcf4a9 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -56,8 +56,8 @@ open class AccessibilityHandler { public var accessibilityId: String? ///This property is used to post accessibility to the UIElement mapped to this accessibilityId public var previousAccessiblityElement: Any? ///This property is capture accessiblity element public var anyCancellable: Set = [] - public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol } - public weak var delegateObject: MVMCoreUIDelegateObject? + public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol } + public var delegateObject: MVMCoreUIDelegateObject? private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("messageStyle") != nil } private let accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() From 9c43649cceb86b81aacbf994fd59c7b931693524 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 2 Nov 2023 21:40:01 +0530 Subject: [PATCH 099/112] added review comments --- MVMCoreUI/Accessibility/AccessibilityHandler.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index 2cfcf4a9..65ae8272 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -53,12 +53,14 @@ open class AccessibilityHandler { guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil } return MVMCoreActionUtility.fatalClassCheck(object: shared) } + //TODO: Revisit to avoid state properties to store in handler. public var accessibilityId: String? ///This property is used to post accessibility to the UIElement mapped to this accessibilityId public var previousAccessiblityElement: Any? ///This property is capture accessiblity element public var anyCancellable: Set = [] - public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol } + public weak var currentController: UIViewController? { MVMCoreUIUtility.getCurrentVisibleController() } public var delegateObject: MVMCoreUIDelegateObject? - private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("messageStyle") != nil } + //TODO: Revisit to identify the page has top notification or not. + private var hasTopNotificationInPage: Bool { (currentController as? MVMCoreViewControllerProtocol)?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || (currentController as? MVMCoreViewControllerProtocol)?.loadObject??.responseInfoMap?.optionalStringForKey("messageStyle") != nil } private let accessibilityOperationQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 @@ -103,7 +105,7 @@ open class AccessibilityHandler { If navigationBar is hidden then we are returning nil so that voice over will shift to the first interactive element. */ open func getFirstFocusedElementOnScreen() -> Any? { - ((delegate as? PageProtocol)?.pageModel?.navigationBar?.hidden ?? false) ? nil : (delegate as? UIViewController)?.navigationController?.navigationBar + ((currentController as? PageProtocol)?.pageModel?.navigationBar?.hidden ?? false) ? nil : currentController?.navigationController?.navigationBar } /** @@ -115,7 +117,7 @@ open class AccessibilityHandler { This method is used to identify the UIElement that is mapped to accessibilityId from server response. */ func getPreDefinedFocusedElementIfAny() -> UIView? { - guard let accessibilityId, let view = (delegate as? UIViewController)?.view else { return nil } + guard let accessibilityId, let view = currentController?.view else { return nil } return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [view]).first { $0.model?.id == accessibilityId } From a3c4c2248cf8eaad445668dfd1186092cbfea3e7 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Thu, 2 Nov 2023 23:06:05 +0530 Subject: [PATCH 100/112] removed unused code & added hidden check --- MVMCoreUI/Accessibility/RotorHandler.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MVMCoreUI/Accessibility/RotorHandler.swift b/MVMCoreUI/Accessibility/RotorHandler.swift index d529564b..a4546980 100644 --- a/MVMCoreUI/Accessibility/RotorHandler.swift +++ b/MVMCoreUI/Accessibility/RotorHandler.swift @@ -24,8 +24,6 @@ fileprivate enum RotorType: String, CaseIterable { return .header case .link: return .link - default: - return .none } } @@ -41,7 +39,7 @@ fileprivate enum RotorType: String, CaseIterable { var filter: ((UIView) -> Bool) { switch self { default: - return { $0.accessibilityTraits.contains(trait) } + return { $0.accessibilityTraits.contains(trait) && !$0.isHidden } } } From 8e4ad2c41e25dd6f5b1638dff30a4c2bf497b6d1 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Fri, 3 Nov 2023 00:11:16 +0530 Subject: [PATCH 101/112] Updated accessibilityText in ListItemModel for searchResults announcement --- MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index a5320ad2..15af9686 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -18,6 +18,7 @@ public var hideArrow: Bool? public var line: LineModel? public var style: ListItemStyle? + public var accessibilityText: String? //-------------------------------------------------- // MARK: - Keys @@ -29,6 +30,7 @@ case hideArrow case line case style + case accessibilityText } //-------------------------------------------------- @@ -102,6 +104,7 @@ hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) style = try typeContainer.decodeIfPresent(ListItemStyle.self, forKey: .style) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) try super.init(from: decoder) } @@ -113,5 +116,6 @@ try container.encodeIfPresent(hideArrow, forKey: .hideArrow) try container.encodeIfPresent(line, forKey: .line) try container.encodeIfPresent(style, forKey: .style) + try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) } } From 61916e7c380515774b3494fdfc571b328792dc34 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 15 Nov 2023 17:14:51 -0500 Subject: [PATCH 102/112] Move hightlightTab to @MainActor to prevent block crash. --- .../HorizontalCombinationViews/TabBar.swift | 18 ++++++++---------- .../Atomic/Protocols/TabBarProtocol.swift | 4 ++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index 5caedcf0..990add73 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -82,20 +82,18 @@ import VDSColorTokens } // MARK: - TabBarProtocol + @MainActor public func highlightTab(at index: Int) { - MVMCoreDispatchUtility.performBlock(onMainThread: { - guard let newSelectedItem = self.items?[index] else { return } - self.tabModel.selectedTab = index - self.selectedItem = newSelectedItem - }) + guard let newSelectedItem = self.items?[index] else { return } + self.tabModel.selectedTab = index + self.selectedItem = newSelectedItem } + @MainActor public func selectTab(at index: Int) { - MVMCoreDispatchUtility.performBlock(onMainThread: { - guard let newSelectedItem = self.items?[index] else { return } - self.selectedItem = newSelectedItem - self.tabBar(self, didSelect: newSelectedItem) - }) + guard let newSelectedItem = self.items?[index] else { return } + self.selectedItem = newSelectedItem + self.tabBar(self, didSelect: newSelectedItem) } public func currentTabIndex() -> Int { tabModel.selectedTab } diff --git a/MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift b/MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift index 1ee147f7..b57b7cdd 100644 --- a/MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TabBarProtocol.swift @@ -13,10 +13,10 @@ import Foundation var delegateObject: MVMCoreUIDelegateObject? { get set } /// Should visually select the given tab index. - @objc func highlightTab(at index: Int) + @MainActor func highlightTab(at index: Int) /// Should select the tab index. As if the user selected it. - @objc func selectTab(at index: Int) + @MainActor func selectTab(at index: Int) /// Returns the current tab @objc func currentTabIndex() -> Int From 4687d7d344adde65891185a37230a80e97bdd0b3 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 15 Nov 2023 17:30:18 -0500 Subject: [PATCH 103/112] self cleanups --- .../HorizontalCombinationViews/TabBar.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index 990add73..605acc3d 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -84,16 +84,16 @@ import VDSColorTokens // MARK: - TabBarProtocol @MainActor public func highlightTab(at index: Int) { - guard let newSelectedItem = self.items?[index] else { return } - self.tabModel.selectedTab = index - self.selectedItem = newSelectedItem + guard let newSelectedItem = items?[index] else { return } + tabModel.selectedTab = index + selectedItem = newSelectedItem } @MainActor public func selectTab(at index: Int) { - guard let newSelectedItem = self.items?[index] else { return } - self.selectedItem = newSelectedItem - self.tabBar(self, didSelect: newSelectedItem) + guard let newSelectedItem = items?[index] else { return } + selectedItem = newSelectedItem + tabBar(self, didSelect: newSelectedItem) } public func currentTabIndex() -> Int { tabModel.selectedTab } From 8de78987f88a851cea33a75500841496e0d9df3a Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Fri, 1 Dec 2023 14:32:44 +0530 Subject: [PATCH 104/112] Setting accessibility label if available before text for eyebrow, headline, body labels in EyebrowHeadlineBodyLink molecule's accessibility label --- .../VerticalCombinationViews/EyebrowHeadlineBodyLink.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index a25426d7..da5e959e 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -88,15 +88,15 @@ var message = "" - if let eyebrowLabel = eyebrow.text { + if let eyebrowLabel = eyebrow.accessibilityLabel ?? eyebrow.text { message += eyebrowLabel + ", " } - if let headlineLabel = headline.text { + if let headlineLabel = headline.accessibilityLabel ?? headline.text { message += headlineLabel + ", " } - if let bodyLabel = body.text { + if let bodyLabel = body.accessibilityLabel ?? body.text { message += bodyLabel } From 6e5980af64c51c1e0998b06d5d8e1b2b56c1b9c9 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 1 Dec 2023 14:12:04 -0500 Subject: [PATCH 105/112] Index check before switching tabs. --- .../HorizontalCombinationViews/TabBar.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index 605acc3d..01e71db8 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -84,16 +84,22 @@ import VDSColorTokens // MARK: - TabBarProtocol @MainActor public func highlightTab(at index: Int) { - guard let newSelectedItem = items?[index] else { return } + guard let items = items, index < items.count else { + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available (\(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + return + } tabModel.selectedTab = index - selectedItem = newSelectedItem + selectedItem = items[index] } @MainActor public func selectTab(at index: Int) { - guard let newSelectedItem = items?[index] else { return } - selectedItem = newSelectedItem - tabBar(self, didSelect: newSelectedItem) + guard let items = items, index < items.count else { + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available (\(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + return + } + selectedItem = items[index] + tabBar(self, didSelect: items[index]) } public func currentTabIndex() -> Int { tabModel.selectedTab } From 21f24b2c9b339e8d5232383caedbd0ffc67694b9 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 1 Dec 2023 14:16:00 -0500 Subject: [PATCH 106/112] remove extra paranthesis --- .../Atomic/Molecules/HorizontalCombinationViews/TabBar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index 01e71db8..d0252419 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -85,7 +85,7 @@ import VDSColorTokens @MainActor public func highlightTab(at index: Int) { guard let items = items, index < items.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available (\(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available \(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) return } tabModel.selectedTab = index @@ -95,7 +95,7 @@ import VDSColorTokens @MainActor public func selectTab(at index: Int) { guard let items = items, index < items.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available (\(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available \(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) return } selectedItem = items[index] From 8b64b2b52a458fb7ed7d23bb0de24e353dbb85a9 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 1 Dec 2023 14:32:33 -0500 Subject: [PATCH 107/112] Ensure index > 0 for those tricky ones. --- .../Molecules/HorizontalCombinationViews/TabBar.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index d0252419..65b116f5 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -84,8 +84,8 @@ import VDSColorTokens // MARK: - TabBarProtocol @MainActor public func highlightTab(at index: Int) { - guard let items = items, index < items.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available \(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + guard let items = items, index >= 0, index < items.count else { + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Invalid tab index \(index). \(items?.count ?? 0) tabs available .", code: 0, domain: ErrorDomainSystem, location: #function)!) return } tabModel.selectedTab = index @@ -94,8 +94,8 @@ import VDSColorTokens @MainActor public func selectTab(at index: Int) { - guard let items = items, index < items.count else { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Tab index \(index) is greater than the number of tabs available \(items?.count ?? 0).", code: 0, domain: ErrorDomainSystem, location: #function)!) + guard let items = items, index >= 0, index < items.count else { + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Invalid tab index \(index). \(items?.count ?? 0) tabs available.", code: 0, domain: ErrorDomainSystem, location: #function)!) return } selectedItem = items[index] From 172173eb1bf700cac5111cf9a8036b7aa6b6640a Mon Sep 17 00:00:00 2001 From: "Rajendran, Nandhini" Date: Mon, 4 Dec 2023 11:53:41 +0000 Subject: [PATCH 108/112] Revert "Merge branch 'feature/ONEAPP-5868' into 'develop'" This reverts merge request !1010 --- .../Atoms/Views/CarouselIndicator/BarsIndicatorView.swift | 4 +--- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift index 4cff7bdb..2eefd716 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift @@ -213,9 +213,7 @@ open class BarsIndicatorView: CarouselIndicator { let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1)) else { return } - let accessibilityValue = String(format: accessibleValueFormat, accessibleIndex, numberOfPages) - view.accessibilityLabel = accessibilityValue - view.accessibilityIdentifier = accessibilityValue + view.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages) } public override func assessTouchOf(_ touchPoint_X: CGFloat) { diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index cb618524..6b360120 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -372,9 +372,7 @@ open class Carousel: View { self.carouselAccessibilityElement = carouselAccessibilityElement } - if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), let pagingView = self.pagingView { - _accessibilityElements = [currentCell, carouselAccessibilityElement, pagingView] - } else if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { + if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { _accessibilityElements = [currentCell, carouselAccessibilityElement] } else { _accessibilityElements = [carouselAccessibilityElement] From cac849fadc48c192761d0656b21ae0ac4e4cdbba Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 11 Dec 2023 10:48:37 -0500 Subject: [PATCH 109/112] text field Spanish localization for optional --- MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift | 2 +- MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings | 2 +- .../SupportingFiles/Strings/es-MX.lproj/Localizable.strings | 1 + MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift index c7552047..aee432e8 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift @@ -64,7 +64,7 @@ public class FormLabelModel: EnableableModelProtocol { if enabled { required.attributes = [LabelAttributeColorModel(FormLabelModel.defaultRequiredTextColor, model.text.count + 1, 8)] } - required.text = "\(model.text) Optional" + required.text = "\(model.text) \(MVMCoreUIUtility.hardcodedString(withKey: "textfield_optional") ?? "")" return required } diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index 0b736bf9..c5b9478e 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -30,7 +30,7 @@ "textfield_picker_item" = " picker item"; "textfield_regular" = " regular"; "textfield_disabled_state" = "disabled"; - +"textfield_optional" = "Optional"; // MARK: MDNTextfield "textfield_contacts_barbutton" = "My Contacts"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings index b279ccdb..b68dd624 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings @@ -26,6 +26,7 @@ "textfield_picker_item" = " artículo de selector"; "textfield_regular" = " regular"; "textfield_disabled_state" = "inactivo"; +"textfield_optional" = "Opcional"; //MDNTextfield "textfield_contacts_barbutton" = "Mis contactos"; "textfield_phone_format_error_message" = "Formato de número de teléfono inválido."; diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index 150ac167..7eb53fa7 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -26,6 +26,7 @@ "textfield_picker_item" = " artículo de selector"; "textfield_regular" = " regular"; "textfield_disabled_state" = "inactivo"; +"textfield_optional" = "Opcional"; //MDNTextfield "textfield_contacts_barbutton" = "Mis contactos"; "textfield_phone_format_error_message" = "Formato de número de teléfono inválido."; From ad48df224219425fee72b33c3dad483aec756b01 Mon Sep 17 00:00:00 2001 From: "Rajendran, Nandhini" Date: Thu, 21 Dec 2023 16:28:22 +0000 Subject: [PATCH 110/112] story: ONEAPP-4156 LoggingHandler Swift migration --- MVMCoreUI.xcodeproj/project.pbxproj | 14 +++++------ .../Atomic/Atoms/Views/Label/Label.swift | 10 +++----- .../Atomic/Atoms/Views/Video/Video.swift | 2 +- .../OtherContainers/ModuleMolecule.swift | 2 +- .../Protocols/MoleculeViewProtocol.swift | 2 +- .../BaseControllers/ViewController.swift | 2 +- .../PageBehaviorHandlerProtocol.swift | 2 +- MVMCoreUI/FormUIHelpers/FormValidator.swift | 2 +- MVMCoreUI/MVMCoreUI.h | 1 - .../Notification/NotificationHandler.swift | 2 +- .../OtherHandlers/MVMCoreUILoggingHandler.h | 24 ------------------- .../OtherHandlers/MVMCoreUILoggingHandler.m | 23 ------------------ .../MVMCoreUILoggingHandler.swift | 21 ++++++++++++++++ MVMCoreUI/Utility/MFFonts.m | 7 ++++-- 14 files changed, 42 insertions(+), 72 deletions(-) delete mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h delete mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m create mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 8619ce46..e8c2d064 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,8 +168,10 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; + 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; }; 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; 71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; }; + 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; }; @@ -473,8 +475,6 @@ D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */; }; D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF26A21E6AA0B003B2FB9 /* FLAnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF26B21E6AA0B003B2FB9 /* FLAnimatedImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */; }; D29DF27921E7A533003B2FB9 /* MVMCoreUISession.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */; }; D29DF28021E7AA51003B2FB9 /* MVMCoreUIDetailViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27F21E7AA50003B2FB9 /* MVMCoreUIDetailViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -760,8 +760,10 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; + 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = ""; }; 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = ""; }; + 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = ""; }; @@ -1073,8 +1075,6 @@ D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLAnimatedImageView.m; sourceTree = ""; }; D29DF26A21E6AA0B003B2FB9 /* FLAnimatedImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLAnimatedImage.h; sourceTree = ""; }; D29DF26B21E6AA0B003B2FB9 /* FLAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLAnimatedImageView.h; sourceTree = ""; }; - D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUILoggingHandler.h; sourceTree = ""; }; - D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUILoggingHandler.m; sourceTree = ""; }; D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUISession.h; sourceTree = ""; }; D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISession.m; sourceTree = ""; }; D29DF27F21E7AA50003B2FB9 /* MVMCoreUIDetailViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIDetailViewProtocol.h; sourceTree = ""; }; @@ -2291,8 +2291,7 @@ D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */, D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */, D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */, - D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */, - D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, + 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */, AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */, D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, @@ -2530,7 +2529,6 @@ D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */, D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */, D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */, - D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, D20492A424329A2800A5EED6 /* MVMCoreUIPagingProtocol.h in Headers */, D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */, @@ -2755,6 +2753,7 @@ D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */, + 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */, 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */, @@ -2762,7 +2761,6 @@ D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */, AAA905E124D1759A00D1EFAB /* ListThreeColumnBillHistory.swift in Sources */, C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */, - D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D2ED27EC254B0CE700A1C293 /* UIAlertControllerStyle+Extension.swift in Sources */, C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 9af907e1..59611756 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -231,7 +231,7 @@ public typealias ActionBlock = () -> () documentAttributes: nil) } catch { if let coreErrorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "LabelHTMLParse") { - MVMCoreUILoggingHandler.addError(toLog: coreErrorObject) + MVMCoreUILoggingHandler.shared()?.addError(toLog: coreErrorObject) } } } @@ -1013,17 +1013,13 @@ extension Label { func validateAttribute(range: NSRange, in string: NSAttributedString, type: String = "") -> NSRange? { guard range.location >= 0 && range.location <= string.length else { - if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) { - loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - } + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) return nil } if type != "image" && range.upperBound > string.length { let newRange = NSRange(location: range.location, length: string.length - range.location) - if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) { - loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) - } + MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!) return newRange } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift index a378b854..baee04f6 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift @@ -89,7 +89,7 @@ open class Video: View { }) case .failed: if let errorObject = item.loadFailedError { - MVMCoreLoggingHandler.addError(toLog: errorObject) + MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) } default: break diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift index 6083ac5e..ce76d379 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift @@ -75,7 +75,7 @@ open class ModuleMolecule: Container { let _ = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { error?.pointee = errorObject - MVMCoreUILoggingHandler.addError(toLog: errorObject) + MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject) } return nil } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 7dd4fa26..c3c07b6a 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -77,7 +77,7 @@ public extension ModelRegistry { return type } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { - MVMCoreLoggingHandler.addError(toLog: errorObject) + MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) } return nil } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index d91e776e..d3d7c3c0 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -115,7 +115,7 @@ import MVMCore }) } catch { if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") { - MVMCoreLoggingHandler.addError(toLog: coreError) + MVMCoreLoggingHandler.shared()?.addError(toLog: coreError) } } } diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift index 9e5c6a6d..325f8927 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift @@ -33,7 +33,7 @@ public extension PageBehaviorHandlerProtocol { behaviors.append(behavior) } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { - MVMCoreLoggingHandler.addError(toLog: errorObject) + MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) } } } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index ec06542c..3aa50030 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -95,7 +95,7 @@ import MVMCore groupValid = try validateGroup(group) } catch { if let err = MVMCoreErrorObject.createErrorObject(for: error, location: "FormValidator"){ - MVMCoreLoggingHandler.addError(toLog: err) + MVMCoreLoggingHandler.shared()?.addError(toLog: err) fatalError(err.description) } } diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index fa2aaf0b..23e40209 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #pragma mark - OtherHandlers #import -#import #import #import diff --git a/MVMCoreUI/Notification/NotificationHandler.swift b/MVMCoreUI/Notification/NotificationHandler.swift index 9ecc2efb..1c7504ee 100644 --- a/MVMCoreUI/Notification/NotificationHandler.swift +++ b/MVMCoreUI/Notification/NotificationHandler.swift @@ -399,7 +399,7 @@ open class NotificationHandler { try await showNotification(for: json, delegateObject: delegateObject) } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") { - MVMCoreUILoggingHandler.addError(toLog: errorObject) + MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject) } } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h deleted file mode 100644 index a4b66ef9..00000000 --- a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// MVMCoreUILoggingHandler.h -// MVMCoreUI -// -// Created by Scott Pfeil on 1/10/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -@import MVMCore.MVMCoreLoggingHandler; - -NS_ASSUME_NONNULL_BEGIN - -@interface MVMCoreUILoggingHandler : MVMCoreLoggingHandler - -// Page State Logging -- (void)defaultLogPageStateForController:(nonnull id )controller; - -// Action Logging -- (void)defaultLogActionForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; -- (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m deleted file mode 100644 index 45a13acf..00000000 --- a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.m +++ /dev/null @@ -1,23 +0,0 @@ -// -// MVMCoreUILoggingHandler.m -// MVMCoreUI -// -// Created by Scott Pfeil on 1/10/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreUILoggingHandler.h" - -@implementation MVMCoreUILoggingHandler - -- (void)defaultLogPageStateForController:(nonnull id )controller { -} - -- (void)defaultLogActionForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData { -} - -- (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id )controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData { - return nil; -} - -@end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift new file mode 100644 index 00000000..edad1b5d --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift @@ -0,0 +1,21 @@ +// +// MVMCoreUILoggingHandler.swift +// MVMCoreUI +// +// Created by Nandhini Rajendran on 29/09/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +@objc open class MVMCoreUILoggingHandler: MVMCoreLoggingHandler { + + // Page State Logging + @objc open func defaultLogPageState(forController controller: MVMCoreViewControllerProtocol) { } + + // Action Logging + @objc open func defaultLogAction(forController controller: MVMCoreViewControllerProtocol?, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { } + + @objc open func defaultGetActionTrackDataDictionary(forController controller: MVMCoreViewControllerProtocol?, actionInformation: NSDictionary?, additionalData: NSDictionary?) -> NSDictionary? { + return nil + } +} + diff --git a/MVMCoreUI/Utility/MFFonts.m b/MVMCoreUI/Utility/MFFonts.m index d16f15ec..30ee68f0 100644 --- a/MVMCoreUI/Utility/MFFonts.m +++ b/MVMCoreUI/Utility/MFFonts.m @@ -9,8 +9,11 @@ #import "MFFonts.h" #import #import "MVMCoreUIUtility.h" -@import MVMCore.MVMCoreLoggingHandler; +#import +@import MVMCore.Swift; @import MVMCore.MVMCoreErrorConstants; +@import MVMCore.MVMCoreLoadHandler; +@import MVMCore.MVMCoreErrorObject; NSString * const DSBold = @"VerizonNHGeDS-Bold"; NSString * const DSRegular = @"VerizonNHGeDS-Regular"; @@ -108,7 +111,7 @@ NSString * const TXRegular = @"VerizonNHGeTX-Regular"; + (void)validFont:(UIFont *)font fontName:(NSString *)fontName { if (font == nil) { MVMCoreErrorObject *errorObject = [[MVMCoreErrorObject alloc] initWithTitle:@"font can not load" message:[NSString stringWithFormat:@"missing font name is %@", fontName] code:ErrorCodeFontNotFound domain:ErrorDomainNative location:@"MFStyler"]; - [MVMCoreLoggingHandler addErrorToLog:errorObject]; + [[MVMCoreLoggingHandler sharedLoggingHandler]addErrorToLog:errorObject]; } } @end From 72358966bc1cee9fd46df39dd6fe4c299a5c4739 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 21 Dec 2023 12:32:08 -0500 Subject: [PATCH 111/112] Remove unused stub. --- MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift index edad1b5d..e74bc580 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift +++ b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingHandler.swift @@ -13,9 +13,4 @@ // Action Logging @objc open func defaultLogAction(forController controller: MVMCoreViewControllerProtocol?, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { } - - @objc open func defaultGetActionTrackDataDictionary(forController controller: MVMCoreViewControllerProtocol?, actionInformation: NSDictionary?, additionalData: NSDictionary?) -> NSDictionary? { - return nil - } } - From c2c4e0f1f4cebb8ea1f844e1c4843050f59d2f3b Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Tue, 2 Jan 2024 17:24:42 -0500 Subject: [PATCH 112/112] indexing safety --- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 2 +- MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 6b360120..2ad92343 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -87,7 +87,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, + guard let model = model as? CarouselModel, !model.molecules.isEmpty, (model.paging == true || loop == true) else { return } DispatchQueue.main.async { self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 5bfd8d0f..04e98287 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -8,7 +8,6 @@ import UIKit - @objcMembers public class CarouselModel: ParentMoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- @@ -57,14 +56,14 @@ import UIKit guard selectable else { // Use visible item value, else index - if let fieldValue = molecules[index].formFieldValue() { + if let fieldValue = molecules[safe: index]?.formFieldValue() { return fieldValue } return index } // Use selected item value, else index guard let selectedIndex = selectedIndex else { return nil } - guard let fieldValue = molecules[selectedIndex].formFieldValue() else { return selectedIndex } + guard let fieldValue = molecules[safe: selectedIndex]?.formFieldValue() else { return selectedIndex } return fieldValue }